Skip to content

Commit

Permalink
Support query params
Browse files Browse the repository at this point in the history
  • Loading branch information
wwwillchen committed Aug 2, 2024
1 parent 8d0108f commit d43f69a
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 2 deletions.
1 change: 1 addition & 0 deletions mesop/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@
)
from mesop.features import page as page
from mesop.features.theme import set_theme_density as set_theme_density
from mesop.features.query_params import query_params as query_params
from mesop.features.theme import set_theme_mode as set_theme_mode
from mesop.features.theme import theme_brightness as theme_brightness
from mesop.features.theme import theme_var as theme_var
Expand Down
1 change: 1 addition & 0 deletions mesop/examples/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from mesop.examples import on_load_generator as on_load_generator
from mesop.examples import playground as playground
from mesop.examples import playground_critic as playground_critic
from mesop.examples import query_params as query_params
from mesop.examples import readme_app as readme_app
from mesop.examples import scroll_into_view as scroll_into_view
from mesop.examples import starter_kit as starter_kit
Expand Down
21 changes: 21 additions & 0 deletions mesop/examples/query_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import mesop as me


def on_load(e: me.LoadEvent):
pass


# me.query_params["foo"] = "bar"
# me.query_params["bar"] = "baz"


@me.page(path="/examples/query_params", on_load=on_load)
def page():
me.text(f"query params: {me.query_params}")
me.button("increment_query_param by navigate", on_click=navigate)


def navigate(e: me.ClickEvent):
print('me.query_params["counter"]', me.query_params["counter"])
counter = int(me.query_params["counter"]) + 1
me.navigate("/examples/query_params?counter=" + str(counter))
30 changes: 30 additions & 0 deletions mesop/features/query_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from typing import Iterator, MutableMapping

from mesop.runtime import runtime


class QueryParamsProxy(MutableMapping[str, str]):
def __iter__(self) -> Iterator[str]:
return iter(runtime().context().query_params())

def __len__(self) -> int:
return len(runtime().context().query_params())

def __str__(self) -> str:
return str(runtime().context().query_params())

def __getitem__(self, key: str) -> str:
print(
"runtime().context().query_params()", runtime().context().query_params()
)
# check if it should be the first or last item
return runtime().context().query_params()[key][0]

def __delitem__(self, key: str) -> None:
runtime().context().set_query_param(key=key, value=None)

def __setitem__(self, key: str, value: str) -> None:
runtime().context().set_query_param(key=key, value=value)


query_params = QueryParamsProxy()
16 changes: 15 additions & 1 deletion mesop/protos/ui.proto
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,16 @@ message UiRequest {
message InitRequest {
optional ViewportSize viewport_size = 1;
optional ThemeSettings theme_settings = 2;
repeated QueryParam query_params = 3;
}

// Next ID: 15
message QueryParam {
optional string key = 1;
// Note: a query param can be repeated, which is why it can have multiple values.
repeated string values = 2;
}

// Next ID: 16
message UserEvent {
optional States states = 1;

Expand All @@ -28,6 +35,7 @@ message UserEvent {

optional ViewportSize viewport_size = 11;
optional ThemeSettings theme_settings = 13;
repeated QueryParam query_params = 15;

oneof type {
bool bool_value = 4;
Expand Down Expand Up @@ -149,6 +157,7 @@ message UpdateStateEvent {
message Command {
oneof command {
NavigateCommand navigate = 1;
UpdateQueryParam update_query_param = 4;
ScrollIntoViewCommand scroll_into_view = 2;
SetThemeMode set_theme_mode = 3;
SetThemeDensity set_theme_density = 4;
Expand All @@ -160,6 +169,11 @@ message NavigateCommand {
optional string url = 1;
}

message UpdateQueryParam {
optional string key = 1;
optional string value = 2;
}

message ScrollIntoViewCommand {
// Key of the component to scroll into view
optional string key = 1;
Expand Down
18 changes: 18 additions & 0 deletions mesop/runtime/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,24 @@ def __init__(
self._node_slot_children_count: int | None = None
self._viewport_size: pb.ViewportSize | None = None
self._theme_settings: pb.ThemeSettings | None = None
self._query_params: dict[str, list[str]] = {}

def query_params(self) -> dict[str, list[str]]:
return self._query_params

def initialize_query_params(self, query_params: list[pb.QueryParam]):
print("init query params", query_params)
for query_param in query_params:
self._query_params[query_param.key] = list(query_param.values)

def set_query_param(self, key: str, value: str | None):
if value is None:
del self._query_params[key]
else:
self._query_params[key] = [value]
self._commands.append(
pb.Command(update_query_param=pb.UpdateQueryParam(key=key, value=value))
)

def commands(self) -> list[pb.Command]:
return self._commands
Expand Down
10 changes: 9 additions & 1 deletion mesop/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from mesop.exceptions import format_traceback
from mesop.runtime import runtime
from mesop.server.config import app_config
from mesop.utils.url_utils import get_url_query_params, remove_url_query_param
from mesop.warn import warn

LOCALHOSTS = (
Expand Down Expand Up @@ -109,6 +110,9 @@ def generate_data(ui_request: pb.UiRequest) -> Generator[str, None, None]:
if ui_request.HasField("init"):
runtime().context().set_theme_settings(ui_request.init.theme_settings)
runtime().context().set_viewport_size(ui_request.init.viewport_size)
runtime().context().initialize_query_params(
list(ui_request.init.query_params)
)
page_config = runtime().get_page_config(path=ui_request.path)
if page_config and page_config.on_load:
result = page_config.on_load(
Expand All @@ -133,6 +137,7 @@ def generate_data(ui_request: pb.UiRequest) -> Generator[str, None, None]:
event = ui_request.user_event
runtime().context().set_theme_settings(event.theme_settings)
runtime().context().set_viewport_size(event.viewport_size)
runtime().context().initialize_query_params(list(event.query_params))

if event.states.states:
runtime().context().update_state(event.states)
Expand Down Expand Up @@ -168,7 +173,10 @@ def generate_data(ui_request: pb.UiRequest) -> Generator[str, None, None]:
)
for command in runtime().context().commands():
if command.HasField("navigate"):
path = command.navigate.url
runtime().context().initialize_query_params(
get_url_query_params(command.navigate.url)
)
path = remove_url_query_param(command.navigate.url)
page_config = runtime().get_page_config(path=path)
if (
page_config
Expand Down
23 changes: 23 additions & 0 deletions mesop/utils/url_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from urllib.parse import urlparse, urlunparse

import mesop.protos.ui_pb2 as pb


def _remove_url_query_param(url: str) -> str:
"""
Expand All @@ -19,3 +21,24 @@ def _escape_url_for_csp(url: str) -> str:

def sanitize_url_for_csp(url: str) -> str:
return _escape_url_for_csp(_remove_url_query_param(url))


def get_url_query_params(url: str) -> list[pb.QueryParam]:
query_param = urlparse(url).query
param_dict: dict[str, list[str]] = {}
if query_param:
for param in query_param.split("&"):
if "=" in param:
key, value = param.split("=", 1)
if key in param_dict:
param_dict[key].append(value)
else:
param_dict[key] = [value]
else:
# Handle cases where there's a key without a value
param_dict[param] = []

param_list: list[pb.QueryParam] = [
pb.QueryParam(key=key, values=values) for key, values in param_dict.items()
]
return param_list
2 changes: 2 additions & 0 deletions mesop/web/src/services/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {SSE} from '../utils/sse';
import {applyComponentDiff, applyStateDiff} from '../utils/diff';
import {getViewportSize} from '../utils/viewport_size';
import {ThemeService} from './theme_service';
import {getQueryParams} from '../utils/query_params';

// Pick 500ms as the minimum duration before showing a progress/busy indicator
// for the channel.
Expand Down Expand Up @@ -236,6 +237,7 @@ export class Channel {
dispatch(userEvent: UserEvent) {
userEvent.setViewportSize(getViewportSize());
userEvent.setThemeSettings(this.themeService.getThemeSettings());
userEvent.setQueryParamsList(getQueryParams());
// Every user event should have an event handler,
// except for the ones below:
if (
Expand Down
22 changes: 22 additions & 0 deletions mesop/web/src/shell/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
UiRequest,
InitRequest,
ThemeMode,
QueryParam,
} from 'mesop/mesop/protos/ui_jspb_proto_pb/mesop/protos/ui_pb';
import {CommonModule} from '@angular/common';
import {
Expand All @@ -37,6 +38,7 @@ import {createCustomElement} from '@angular/elements';
import {Subject} from 'rxjs';
import {debounceTime} from 'rxjs/operators';
import {ThemeService} from '../services/theme_service';
import {getQueryParams} from '../utils/query_params';

@Component({
selector: 'mesop-shell',
Expand Down Expand Up @@ -84,6 +86,14 @@ export class Shell {
const initRequest = new InitRequest();
initRequest.setViewportSize(getViewportSize());
initRequest.setThemeSettings(this.themeService.getThemeSettings());
initRequest.setQueryParamsList(getQueryParams());
// const queryParams = new Map(new URLSearchParams(window.location.search));
// queryParams.forEach((value, key) => {
// const queryParam = new QueryParam();
// queryParam.setKey(key);
// queryParam.setValuesList([value]);
// initRequest.addQueryParams(queryParam);
// });
request.setInit(initRequest);
this.channel.init(
{
Expand Down Expand Up @@ -137,6 +147,18 @@ export class Shell {
throw new Error('Density undefined in setThemeDensity command');
}
this.themeService.setDensity(density);
} else if (command.hasUpdateQueryParam()) {
const updateQueryParam = command.getUpdateQueryParam()!;
updateQueryParam.getKey();
const key = updateQueryParam.getKey()!;
const value = updateQueryParam.getValue();
const url = new URL(window.location.href);
if (value == null) {
url.searchParams.delete(key);
} else {
url.searchParams.set(key, value);
}
window.history.replaceState({}, '', url.toString());
} else {
throw new Error(
'Unhandled command: ' + command.getCommandCase().toString(),
Expand Down
22 changes: 22 additions & 0 deletions mesop/web/src/utils/query_params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {QueryParam} from 'mesop/mesop/protos/ui_jspb_proto_pb/mesop/protos/ui_pb';

export function getQueryParams(): QueryParam[] {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const params: Record<string, string[]> = {};
urlParams.forEach((value, key) => {
if (!params[key]) {
params[key] = [value];
} else {
params[key].push(value);
}
});
const queryParams: QueryParam[] = [];
for (const [key, values] of Object.entries(params)) {
const queryParam = new QueryParam();
queryParam.setKey(key);
queryParam.setValuesList(values);
queryParams.push(queryParam);
}
return queryParams;
}

0 comments on commit d43f69a

Please sign in to comment.