-
Notifications
You must be signed in to change notification settings - Fork 339
Create a @serve decorator to let developers configure routes manually. #1288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,2 @@ | ||
| from .page import page as page | ||
| from .serve import server as server | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| from typing import Callable | ||
|
|
||
| from mesop.runtime import runtime | ||
|
|
||
|
|
||
| def serve( | ||
| *, | ||
| rule: str, | ||
| ) -> Callable[[Callable[[], None]], Callable[[], None]]: | ||
| def decorator(func: Callable[[], None]) -> Callable[[], None]: | ||
| runtime().register_handler( | ||
| rule=rule, | ||
| handler=func, | ||
| ) | ||
| return func | ||
|
|
||
| return decorator |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| from copy import deepcopy | ||
| from dataclasses import dataclass | ||
| from typing import Any, Callable, Generator, Type, TypeVar, cast | ||
| from typing import Any, Callable, Dict, Generator, Tuple, Type, TypeVar, cast | ||
|
|
||
| from flask import g, request | ||
|
|
||
|
|
@@ -49,6 +49,9 @@ def __init__(self): | |
| # clients polling whether to request a hot reload. | ||
| self.hot_reload_counter = 0 | ||
| self._path_to_page_config: dict[str, PageConfig] = {} | ||
| self._rule_to_handler: dict[ | ||
| str, Callable[[], Tuple[str, int, Dict[str, str]]] | ||
| ] = {} | ||
| self.component_fns: set[Callable[..., Any]] = set() | ||
| self.event_mappers: dict[Type[Any], Callable[[pb.UserEvent, Key], Any]] = {} | ||
| self._state_classes: list[type[Any]] = [] | ||
|
|
@@ -131,9 +134,27 @@ def register_page(self, *, path: str, page_config: PageConfig) -> None: | |
| ) | ||
| self._path_to_page_config[path] = page_config | ||
|
|
||
| def register_handler( | ||
| self, *, rule: str, handler: Callable[[], Tuple[str, int, Dict[str, str]]] | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [question] Where did you get this callable signature from - Callable[[], Tuple[str, int, Dict[str, str]]? Seems like it's a subset of what Flask expects in RouteCallable (tuple[ResponseValue, int, HeadersValue]?) This does bring up the question of exposing Flask internals. Ideally we'd try to hide the Flask internals, though I think this will be somewhat hard to avoid. Maybe we can use TypeAliases to avoid explicitly in exposing the Flask types in the public serve function. Also the script that does the type checking is not part of the local presubmits. You'll need to run it manually to check the types. Usually something like this from the root of the directory (but assumes you've set up your dev env -- though you can also try to spin up a GitHub Codespaces)
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok I managed to run the typechecker. For the types, we could either copy Flasks's types and use the
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think ANY could be ok here so long as it is internal. For Mesop, we like to have annotations for the public API. Makes it more user friendly, so we need to be consistent with that. We don't have to expose the Flask full type for Mesop. We could try just a subset of what Flask allows. |
||
| ): | ||
| if self._has_served_traffic: | ||
| raise MesopDeveloperException( | ||
| "Cannot register a handler after traffic has been served. You must register all handlers upon server startup before any traffic has been served. This prevents security issues." | ||
| ) | ||
| if rule in self._rule_to_handler: | ||
| raise MesopDeveloperException( | ||
| f"Handler for rule '{rule}' already registered." | ||
| ) | ||
| self._rule_to_handler[rule] = handler | ||
|
|
||
| def get_page_config(self, *, path: str) -> PageConfig | None: | ||
| return self._path_to_page_config.get(path) | ||
|
|
||
| def get_rule_to_handlers( | ||
| self, | ||
| ) -> dict[str, Callable[[], Tuple[str, int, Dict[str, str]]]]: | ||
| return deepcopy(self._rule_to_handler) | ||
|
|
||
| def get_path_to_page_configs(self) -> dict[str, PageConfig]: | ||
| return deepcopy(self._path_to_page_config) | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.