Use websockets for events (#150)

This commit is contained in:
Nikhil Rao 2022-12-20 15:22:04 -08:00 committed by GitHub
parent 84ca907aac
commit 0b496fc0e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1257 additions and 1085 deletions

87
poetry.lock generated
View File

@ -604,14 +604,14 @@ plugins = ["importlib-metadata"]
[[package]]
name = "pyright"
version = "1.1.283"
version = "1.1.284"
description = "Command line wrapper for pyright"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "pyright-1.1.283-py3-none-any.whl", hash = "sha256:d5b14bb50ccbfb7769e09a611960665021816ea1b0543e0ffb8c341388c73710"},
{file = "pyright-1.1.283.tar.gz", hash = "sha256:57dcc7abf0b764d5f587a80faf20186f5666adffe5f34afa10d471e96bbd9509"},
{file = "pyright-1.1.284-py3-none-any.whl", hash = "sha256:e3bfbd33c20af48eed9d20138767265161ba8a4b55c740476a36ce822bd482d1"},
{file = "pyright-1.1.284.tar.gz", hash = "sha256:ef7c0e46e38be95687f5a0633e55c5171ca166048b9560558168a976162e287c"},
]
[package.dependencies]
@ -1020,6 +1020,85 @@ typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
[package.extras]
standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
[[package]]
name = "websockets"
version = "10.4"
description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "websockets-10.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d58804e996d7d2307173d56c297cf7bc132c52df27a3efaac5e8d43e36c21c48"},
{file = "websockets-10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc0b82d728fe21a0d03e65f81980abbbcb13b5387f733a1a870672c5be26edab"},
{file = "websockets-10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba089c499e1f4155d2a3c2a05d2878a3428cf321c848f2b5a45ce55f0d7d310c"},
{file = "websockets-10.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33d69ca7612f0ddff3316b0c7b33ca180d464ecac2d115805c044bf0a3b0d032"},
{file = "websockets-10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62e627f6b6d4aed919a2052efc408da7a545c606268d5ab5bfab4432734b82b4"},
{file = "websockets-10.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ea7b82bfcae927eeffc55d2ffa31665dc7fec7b8dc654506b8e5a518eb4d50"},
{file = "websockets-10.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e0cb5cc6ece6ffa75baccfd5c02cffe776f3f5c8bf486811f9d3ea3453676ce8"},
{file = "websockets-10.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae5e95cfb53ab1da62185e23b3130e11d64431179debac6dc3c6acf08760e9b1"},
{file = "websockets-10.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7c584f366f46ba667cfa66020344886cf47088e79c9b9d39c84ce9ea98aaa331"},
{file = "websockets-10.4-cp310-cp310-win32.whl", hash = "sha256:b029fb2032ae4724d8ae8d4f6b363f2cc39e4c7b12454df8df7f0f563ed3e61a"},
{file = "websockets-10.4-cp310-cp310-win_amd64.whl", hash = "sha256:8dc96f64ae43dde92530775e9cb169979f414dcf5cff670455d81a6823b42089"},
{file = "websockets-10.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47a2964021f2110116cc1125b3e6d87ab5ad16dea161949e7244ec583b905bb4"},
{file = "websockets-10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e789376b52c295c4946403bd0efecf27ab98f05319df4583d3c48e43c7342c2f"},
{file = "websockets-10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d3f0b61c45c3fa9a349cf484962c559a8a1d80dae6977276df8fd1fa5e3cb8c"},
{file = "websockets-10.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f55b5905705725af31ccef50e55391621532cd64fbf0bc6f4bac935f0fccec46"},
{file = "websockets-10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00c870522cdb69cd625b93f002961ffb0c095394f06ba8c48f17eef7c1541f96"},
{file = "websockets-10.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f38706e0b15d3c20ef6259fd4bc1700cd133b06c3c1bb108ffe3f8947be15fa"},
{file = "websockets-10.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f2c38d588887a609191d30e902df2a32711f708abfd85d318ca9b367258cfd0c"},
{file = "websockets-10.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fe10ddc59b304cb19a1bdf5bd0a7719cbbc9fbdd57ac80ed436b709fcf889106"},
{file = "websockets-10.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:90fcf8929836d4a0e964d799a58823547df5a5e9afa83081761630553be731f9"},
{file = "websockets-10.4-cp311-cp311-win32.whl", hash = "sha256:b9968694c5f467bf67ef97ae7ad4d56d14be2751000c1207d31bf3bb8860bae8"},
{file = "websockets-10.4-cp311-cp311-win_amd64.whl", hash = "sha256:a7a240d7a74bf8d5cb3bfe6be7f21697a28ec4b1a437607bae08ac7acf5b4882"},
{file = "websockets-10.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:74de2b894b47f1d21cbd0b37a5e2b2392ad95d17ae983e64727e18eb281fe7cb"},
{file = "websockets-10.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3a686ecb4aa0d64ae60c9c9f1a7d5d46cab9bfb5d91a2d303d00e2cd4c4c5cc"},
{file = "websockets-10.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d15c968ea7a65211e084f523151dbf8ae44634de03c801b8bd070b74e85033"},
{file = "websockets-10.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00213676a2e46b6ebf6045bc11d0f529d9120baa6f58d122b4021ad92adabd41"},
{file = "websockets-10.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e23173580d740bf8822fd0379e4bf30aa1d5a92a4f252d34e893070c081050df"},
{file = "websockets-10.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:dd500e0a5e11969cdd3320935ca2ff1e936f2358f9c2e61f100a1660933320ea"},
{file = "websockets-10.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4239b6027e3d66a89446908ff3027d2737afc1a375f8fd3eea630a4842ec9a0c"},
{file = "websockets-10.4-cp37-cp37m-win32.whl", hash = "sha256:8a5cc00546e0a701da4639aa0bbcb0ae2bb678c87f46da01ac2d789e1f2d2038"},
{file = "websockets-10.4-cp37-cp37m-win_amd64.whl", hash = "sha256:a9f9a735deaf9a0cadc2d8c50d1a5bcdbae8b6e539c6e08237bc4082d7c13f28"},
{file = "websockets-10.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c1289596042fad2cdceb05e1ebf7aadf9995c928e0da2b7a4e99494953b1b94"},
{file = "websockets-10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0cff816f51fb33c26d6e2b16b5c7d48eaa31dae5488ace6aae468b361f422b63"},
{file = "websockets-10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dd9becd5fe29773d140d68d607d66a38f60e31b86df75332703757ee645b6faf"},
{file = "websockets-10.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45ec8e75b7dbc9539cbfafa570742fe4f676eb8b0d3694b67dabe2f2ceed8aa6"},
{file = "websockets-10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f72e5cd0f18f262f5da20efa9e241699e0cf3a766317a17392550c9ad7b37d8"},
{file = "websockets-10.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185929b4808b36a79c65b7865783b87b6841e852ef5407a2fb0c03381092fa3b"},
{file = "websockets-10.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d27a7e34c313b3a7f91adcd05134315002aaf8540d7b4f90336beafaea6217c"},
{file = "websockets-10.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:884be66c76a444c59f801ac13f40c76f176f1bfa815ef5b8ed44321e74f1600b"},
{file = "websockets-10.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:931c039af54fc195fe6ad536fde4b0de04da9d5916e78e55405436348cfb0e56"},
{file = "websockets-10.4-cp38-cp38-win32.whl", hash = "sha256:db3c336f9eda2532ec0fd8ea49fef7a8df8f6c804cdf4f39e5c5c0d4a4ad9a7a"},
{file = "websockets-10.4-cp38-cp38-win_amd64.whl", hash = "sha256:48c08473563323f9c9debac781ecf66f94ad5a3680a38fe84dee5388cf5acaf6"},
{file = "websockets-10.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:40e826de3085721dabc7cf9bfd41682dadc02286d8cf149b3ad05bff89311e4f"},
{file = "websockets-10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:56029457f219ade1f2fc12a6504ea61e14ee227a815531f9738e41203a429112"},
{file = "websockets-10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5fc088b7a32f244c519a048c170f14cf2251b849ef0e20cbbb0fdf0fdaf556f"},
{file = "websockets-10.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc8709c00704194213d45e455adc106ff9e87658297f72d544220e32029cd3d"},
{file = "websockets-10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0154f7691e4fe6c2b2bc275b5701e8b158dae92a1ab229e2b940efe11905dff4"},
{file = "websockets-10.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c6d2264f485f0b53adf22697ac11e261ce84805c232ed5dbe6b1bcb84b00ff0"},
{file = "websockets-10.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9bc42e8402dc5e9905fb8b9649f57efcb2056693b7e88faa8fb029256ba9c68c"},
{file = "websockets-10.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:edc344de4dac1d89300a053ac973299e82d3db56330f3494905643bb68801269"},
{file = "websockets-10.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:84bc2a7d075f32f6ed98652db3a680a17a4edb21ca7f80fe42e38753a58ee02b"},
{file = "websockets-10.4-cp39-cp39-win32.whl", hash = "sha256:c94ae4faf2d09f7c81847c63843f84fe47bf6253c9d60b20f25edfd30fb12588"},
{file = "websockets-10.4-cp39-cp39-win_amd64.whl", hash = "sha256:bbccd847aa0c3a69b5f691a84d2341a4f8a629c6922558f2a70611305f902d74"},
{file = "websockets-10.4-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:82ff5e1cae4e855147fd57a2863376ed7454134c2bf49ec604dfe71e446e2193"},
{file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d210abe51b5da0ffdbf7b43eed0cfdff8a55a1ab17abbec4301c9ff077dd0342"},
{file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:942de28af58f352a6f588bc72490ae0f4ccd6dfc2bd3de5945b882a078e4e179"},
{file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9b27d6c1c6cd53dc93614967e9ce00ae7f864a2d9f99fe5ed86706e1ecbf485"},
{file = "websockets-10.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3d3cac3e32b2c8414f4f87c1b2ab686fa6284a980ba283617404377cd448f631"},
{file = "websockets-10.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:da39dd03d130162deb63da51f6e66ed73032ae62e74aaccc4236e30edccddbb0"},
{file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389f8dbb5c489e305fb113ca1b6bdcdaa130923f77485db5b189de343a179393"},
{file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09a1814bb15eff7069e51fed0826df0bc0702652b5cb8f87697d469d79c23576"},
{file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff64a1d38d156d429404aaa84b27305e957fd10c30e5880d1765c9480bea490f"},
{file = "websockets-10.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b343f521b047493dc4022dd338fc6db9d9282658862756b4f6fd0e996c1380e1"},
{file = "websockets-10.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:932af322458da7e4e35df32f050389e13d3d96b09d274b22a7aa1808f292fee4"},
{file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a4162139374a49eb18ef5b2f4da1dd95c994588f5033d64e0bbfda4b6b6fcf"},
{file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c57e4c1349fbe0e446c9fa7b19ed2f8a4417233b6984277cce392819123142d3"},
{file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b627c266f295de9dea86bd1112ed3d5fafb69a348af30a2422e16590a8ecba13"},
{file = "websockets-10.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:05a7233089f8bd355e8cbe127c2e8ca0b4ea55467861906b80d2ebc7db4d6b72"},
{file = "websockets-10.4.tar.gz", hash = "sha256:eef610b23933c54d5d921c92578ae5f89813438fded840c2e9809d378dc765d3"},
]
[[package]]
name = "zipp"
version = "3.11.0"
@ -1039,4 +1118,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
[metadata]
lock-version = "2.0"
python-versions = "^3.7"
content-hash = "bdbe03e00e254692c4037edb18af3f1c4be6562f95108c9f057691878d09885d"
content-hash = "97dfea67e5b4dd4749628c44911fa43392206c1c77a90191b267f060f95d10c4"

Binary file not shown.

View File

@ -1,3 +1,3 @@
module.exports = {
reactStrictMode: true
reactStrictMode: true,
};

View File

@ -18,7 +18,7 @@
"gridjs-react": "^4.0.0",
"next": "^12.1.0",
"plotly.js": "2.6.4",
"prettier": "^2.7.1",
"prettier": "^2.8.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-markdown": "^8.0.3",
@ -28,4 +28,4 @@
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,16 @@
import axios from "axios";
// State management for Pynecone web apps.
// Global variable to hold the token.
let token;
// Key for the token in the session storage.
const TOKEN_KEY = "token";
/**
* Generate a UUID (Used for session tokens).
* Taken from: https://stackoverflow.com/questions/105034/how-do-i-create-a-guid-uuid
* @returns A UUID.
*/
const generateUUID = () => {
let d = new Date().getTime(),
d2 = (performance && performance.now && performance.now() * 1000) || 0;
@ -19,6 +27,10 @@ const generateUUID = () => {
});
};
/**
* Get the token for the current session.
* @returns The token.
*/
export const getToken = () => {
if (token) {
return token;
@ -32,6 +44,11 @@ export const getToken = () => {
return token;
};
/**
* Apply a delta to the state.
* @param state The state to apply the delta to.
* @param delta The delta to apply.
*/
export const applyDelta = (state, delta) => {
for (const substate in delta) {
let s = state;
@ -45,53 +62,86 @@ export const applyDelta = (state, delta) => {
}
};
export const applyEvent = async (state, event, endpoint, router) => {
/**
* Send an event to the server.
* @param event The event to send.
* @param router The router object.
* @param socket The socket object to send the event on.
*/
export const applyEvent = async (event, router, socket) => {
// Handle special events
if (event.name == "_redirect") {
router.push(event.payload.path);
return [];
return;
}
if (event.name == "_console") {
console.log(event.payload.message);
return [];
return;
}
if (event.name == "_alert") {
alert(event.payload.message);
return [];
return;
}
// Send the event to the server.
event.token = getToken();
const update = (await axios.post(endpoint, event)).data;
applyDelta(state, update.delta);
return update.events;
if (socket) {
socket.send(JSON.stringify(event));
}
};
export const updateState = async (
state,
result,
setResult,
endpoint,
router
) => {
/**
* Process an event off the event queue.
* @param state The state with the event queue.
* @param result The current result
* @param setResult The function to set the result.
* @param router The router object.
* @param socket The socket object to send the event on.
*/
export const updateState = async (state, result, setResult, router, socket) => {
// If we are already processing an event, or there are no events to process, return.
if (result.processing || state.events.length == 0) {
return;
}
// If the socket is not ready, return.
if (!socket.readyState) {
return;
}
// Process the next event in the queue.
setResult({ ...result, processing: true });
const events = await applyEvent(
state,
state.events.shift(),
endpoint,
router
);
setResult({
processing: true,
state: state,
events: events,
});
await applyEvent(state.events.shift(), router, socket);
};
/**
* Connect to a websocket and set the handlers.
* @param socket The socket object to connect.
* @param state The state object to apply the deltas to.
* @param setResult The function to set the result.
* @param endpoint The endpoint to connect to.
*/
export const connect = async (socket, state, setResult, endpoint) => {
socket.current = new WebSocket(endpoint);
socket.current.onmessage = function (update) {
update = JSON.parse(update.data);
applyDelta(state, update.delta);
setResult({
processing: false,
state: state,
events: update.events,
});
};
};
/**
* Create an event object.
* @param name The name of the event.
* @param payload The payload of the event.
* @returns The event object.
*/
export const E = (name, payload) => {
return { name, payload };
};

View File

@ -1,10 +1,8 @@
"""The main Pynecone app."""
import os
import re
from typing import Any, Callable, Coroutine, Dict, List, Optional, Tuple, Type, Union
import fastapi
from fastapi import FastAPI, WebSocket
from fastapi.middleware import cors
from pynecone import constants, utils
@ -32,7 +30,7 @@ class App(Base):
stylesheets: List[str] = []
# The backend API object.
api: fastapi.FastAPI = None # type: ignore
api: FastAPI = None # type: ignore
# The state class to use for the app.
state: Type[State] = DefaultState
@ -62,7 +60,7 @@ class App(Base):
self.state_manager.setup(state=self.state)
# Set up the API.
self.api = fastapi.FastAPI()
self.api = FastAPI()
self.add_cors()
self.add_default_endpoints()
@ -74,7 +72,7 @@ class App(Base):
"""
return f"<App state={self.state.__name__}>"
def __call__(self) -> fastapi.FastAPI:
def __call__(self) -> FastAPI:
"""Run the backend api instance.
Returns:
@ -85,10 +83,10 @@ class App(Base):
def add_default_endpoints(self):
"""Add the default endpoints."""
# To test the server.
self.get(str(constants.Endpoint.PING))(_ping)
self.api.get(str(constants.Endpoint.PING))(_ping)
# To make state changes.
self.post(str(constants.Endpoint.EVENT))(_event(app=self))
self.api.websocket(str(constants.Endpoint.EVENT))(_event(app=self))
def add_cors(self):
"""Add CORS middleware to the app."""
@ -99,32 +97,6 @@ class App(Base):
allow_headers=["*"],
)
def get(self, path: str, *args, **kwargs) -> Callable:
"""Register a get request.
Args:
path: The endpoint path to link to the request.
*args: Args to pass to the request.
**kwargs: Kwargs to pass to the request.
Returns:
A decorator to handle the request.
"""
return self.api.get(path, *args, **kwargs)
def post(self, path: str, *args, **kwargs) -> Callable:
"""Register a post request.
Args:
path: The endpoint path to link to the request.
*args: Args to pass to the request.
**kwargs: Kwargs to pass to the request.
Returns:
A decorator to handle the request.
"""
return self.api.post(path, *args, **kwargs)
def preprocess(self, state: State, event: Event) -> Optional[Delta]:
"""Preprocess the event.
@ -324,35 +296,65 @@ async def _ping() -> str:
return "pong"
def _event(app: App) -> Reducer:
"""Create an event reducer to modify the state.
def _event(app: App):
"""Websocket endpoint for events.
Args:
app: The app to modify the state of.
app: The app to add the endpoint to.
Returns:
A handler that takes in an event and modifies the state.
The websocket endpoint.
"""
async def process(event: Event) -> StateUpdate:
# Get the state for the session.
state = app.get_state(event.token)
async def ws(websocket: WebSocket):
"""Create websocket endpoint.
# Preprocess the event.
pre = app.preprocess(state, event)
if pre is not None:
return StateUpdate(delta=pre)
Args:
websocket: The websocket sending events.
"""
# Accept the connection.
await websocket.accept()
# Apply the event to the state.
update = await state.process(event)
app.set_state(event.token, state)
# Process events until the connection is closed.
while True:
# Get the event.
event = Event.parse_raw(await websocket.receive_text())
# Postprocess the event.
post = app.postprocess(state, event, update.delta)
if post is not None:
return StateUpdate(delta=post)
# Process the event.
update = await process(app, event)
# Return the delta.
return update
# Send the update.
await websocket.send_text(update.json())
return process
return ws
async def process(app: App, event: Event) -> StateUpdate:
"""Process an event.
Args:
app: The app to process the event for.
event: The event to process.
Returns:
The state update after processing the event.
"""
# Get the state for the session.
state = app.get_state(event.token)
# Preprocess the event.
pre = app.preprocess(state, event)
if pre is not None:
return StateUpdate(delta=pre)
# Apply the event to the state.
update = await state.process(event)
app.set_state(event.token, state)
# Postprocess the event.
post = app.postprocess(state, event, update.delta)
if post is not None:
return StateUpdate(delta=post)
# Return the update.
return update

View File

@ -10,9 +10,9 @@ from pynecone.state import State
# Imports to be included in every Pynecone app.
DEFAULT_IMPORTS: ImportDict = {
"react": {"useEffect", "useState"},
"react": {"useEffect", "useRef", "useState"},
"next/router": {"useRouter"},
f"/{constants.STATE_PATH}": {"updateState", "E"},
f"/{constants.STATE_PATH}": {"connect", "updateState", "E"},
"": {"focus-visible/dist/focus-visible"},
}

View File

@ -136,12 +136,16 @@ EVENT_FN = join(
ROUTER = constants.ROUTER
RESULT = constants.RESULT
PROCESSING = constants.PROCESSING
SOCKET = constants.SOCKET
STATE = constants.STATE
EVENTS = constants.EVENTS
SET_RESULT = format_state_setter(RESULT)
USE_EFFECT = join(
[
"useEffect(() => {{",
f" if (!{SOCKET}.current) {{{{",
f" connect({SOCKET}, {{state}}, {SET_RESULT}, {EVENT_ENDPOINT})",
" }}",
" const update = async () => {{",
f" if ({RESULT}.{STATE} != null) {{{{",
f" {{set_state}}({{{{",
@ -154,7 +158,7 @@ USE_EFFECT = join(
f" {PROCESSING}: false,",
" }})",
" }}",
f" await updateState({{state}}, {RESULT}, {SET_RESULT}, {EVENT_ENDPOINT}, {ROUTER})",
f" await updateState({{state}}, {RESULT}, {SET_RESULT}, {ROUTER}, {SOCKET}.current)",
" }}",
" update()",
"}})",
@ -163,3 +167,6 @@ USE_EFFECT = join(
# Routing
ROUTER = f"const {constants.ROUTER} = useRouter()"
# Sockets.
SOCKET = "const socket = useRef(null)"

View File

@ -94,9 +94,6 @@ def compile_constants() -> str:
)
import plotly.graph_objects as go
def compile_state(state: Type[State]) -> str:
"""Compile the state of the app.
@ -126,7 +123,8 @@ def compile_state(state: Type[State]) -> str:
initial_state=json.dumps(initial_result),
)
router = templates.ROUTER
return templates.join([synced_state, result, router])
socket = templates.SOCKET
return templates.join([synced_state, result, router, socket])
def compile_events(state: Type[State]) -> str:

View File

@ -74,6 +74,8 @@ APP_VAR = "app"
API_VAR = "api"
# The name of the router variable.
ROUTER = "router"
# The name of the socket variable.
SOCKET = "socket"
# The name of the variable to hold API results.
RESULT = "result"
# The name of the process variable.
@ -146,7 +148,17 @@ class Endpoint(Enum):
Returns:
The full URL for the endpoint.
"""
# Import here to avoid circular imports.
from pynecone import utils
# Get the API URL from the config.
config = utils.get_config()
return "".join([config.api_url, str(self)])
url = "".join([config.api_url, str(self)])
# The event endpoint is a websocket.
if self == Endpoint.EVENT:
# Replace the protocol with ws.
url = url.replace("https://", "ws://").replace("http://", "ws://")
# Return the url.
return url

View File

@ -34,6 +34,7 @@ uvicorn = "^0.20.0"
rich = "^12.6.0"
redis = "^4.3.5"
httpx = "^0.23.1"
websockets = "^10.4"
[tool.poetry.dev-dependencies]
pytest = "^7.1.2"