google / mesop Goto Github PK
View Code? Open in Web Editor NEWBuild delightful web apps quickly in Python
Home Page: https://google.github.io/mesop/
License: Apache License 2.0
Build delightful web apps quickly in Python
Home Page: https://google.github.io/mesop/
License: Apache License 2.0
It's tedious to manually create wrappers for native Angular components even with a component generator script, particularly because some components may only exist internally in our downstream codebase.
We will still provide a way to manually specify components the current way, for advanced use cases, but for >80% of use cases, we should be able to code-gen the component.
One of the difficulties is that it can produce difficult to understand errors
What it needs to generate?
mesop/__init__.py
Use TS compiler (or just parse AST) of the specified Angular component.
Generate a ComponentNgSpec based on the input and output. This will turn into a proto. We can use this proto to: 1) generate the Python file (may or may not be checked-in) and 2) use it to dynamically resolve the Angular component.
import { Injectable, ViewContainerRef, ComponentRef } from '@angular/core';
import { YourComponent } from './path-to-your-component';
@Injectable({
providedIn: 'root'
})
export class DynamicComponentService {
constructor(private componentFactoryResolver: ComponentFactoryResolver) {}
createComponent(viewContainerRef: ViewContainerRef): ComponentRef<YourComponent> {
const componentRef = viewContainerRef.createComponent(SomeComponent);
// Optional: Interact with the component
// componentRef.instance.someInput = 'some value';
// componentRef.instance.someOutput.subscribe(() => { ... });
return componentRef;
}
}
Steps:
Right now the styles API is basically a string we send directly from Python to Angular.
By providing a strongly-typed, first-class styles API, we can solve several issues:
:hover
).group
and media queries.We don't need to reload the entire page. Instead we just need to trigger a hot reload request from the client side.
Right now the Plot component over supports statically rendering matplotlib figures.
Alternatively, look at other charting libraries like Bokeh which provides a Python API that supports rich interactivity through JS.
Right now, we compile the development version of Angular. We should use the app_bundle target similar to what tensorboard has done.
This will make the JS binary smaller and faster.
If a user rapidly triggers events, we currently have a bug.
Instead, Optic should queue the events so that each one is processed at a time.
MVP: Queue on the client.
In the future, we can consider batching events that are fired close together to process them together in the server, this could reduce a network roundtrip, but this could delay sending intermediate results. It's not clear what's the right behavior and it seems probably use case-dependent.
portpicker.is_port_free
fuser -k {port}/tcp
lsof -t -i:32123 | xargs kill
Got a cryptic error with Base-64 encoding which adds bloat to network payload, but fixed it in wwwillchen@fdbce13. Also, it's a bit strange to expose this on a URL and may lead to issues down the road.
For future reference, here's a potential way to Base-64 encode:
https://github.com/google/closure-library/blob/7818ff7dc0b53555a7fb3c3427e6761e88bde3a2/closure/goog/crypt/base64.js#L161
Go over concepts like:
Right now we don't lint our TypeScript code and it'd be nice to do so to avoid common mistakes like unused imports.
Example usages:
import mesop as me
me.out(state)
Makes it more obvious this is what you should depend upon.
Potentially include other common deps besides cli
that are frequently used.
There's two purposes for the me.on
decorator now:
register the functions with a the qualified function name (module + fn name) so it can be called to process an event update
maps from the general UserEvent type to the specific event type needed
can be pretty easily addressed because we know the event type based on the callsite usage.
For 1), one idea is to first do a trace run, which executes the render loop (without actually rendering) to: a) collect references to all functions and, potentially, b) to compute the initial component tree so we can do a diff and and only send the delta to the client.
I need a reliable way of fingerprinting a function so that if there's two functions with the same name, I'll call the correct one.
Sometimes there's a function we need to call in JavaScript from our Python code.
JS API:
window.foo = (input: str): str => {
return input + " processed";
}
Python API:
import mesop as me
def on_click():
value = await me.call_js("foo", ["arg1"])
print(value) # should be "foo processed"
Look at how tensorboard supports pip packaging.
Because the stack trace for Colab/Jupyter notebooks isn't the same as regular Python files, the source code editing functionality is broken in visual editor when running under Colab.
Native
Material
In editor mode, we show an error screen which includes stack trace.
For prod, we should show an error screen:
404 - Page not found
500 - Internal Server error
Also, we should log an error in console using console.error
but not send the stacktrace from the server to the client.
It's quite tedious to maintain the BUILD files and it's easy to make mistakes, particularly with violating strict dependencies by relying on transitive dependencies.
Unfortunately there isn't any one single tool that can call all of Optic's languages, so we can look into some combination of:
Look into using: https://github.com/markedjs/marked
Other Google projects like Tensorboard have used it (see wrapper)
This will allow much richer text formatting and it can also be used within Optic for better display of error messages.
bottle.py has less dependencies than Flask which makes it easier to sync downstream.
Experience using Flask:
I haven't used Flask extensively, but in the ~month or so developing with Flask, I have found it quite un-intuitive in several aspects. For example, the docs says:
Threaded mode is enabled by default.
Rather, than making it explicitly there's an option called threaded
, you need to read through the Werkzeug's docs to understand this property. In general, the delineation between Flask and Werkzeug seems a bit fuzzy and makes reading the docs and understanding Flask harder. Other people have reported issues with using Flask.
Alternatives:
I considered other frameworks such as Tornado however it doesn't support running a Tornado server in a WSGI container docs
Example usage:
context = request.environ.get('context')
After supporting pip install mesop
(#41), one of the issues with running Mesop on pip is that it doesn't support hot reload. Right now, Mesop's hot reload mechanism relies on ibazel which injects the livereload script and sends a rebuild event.
mesop/mesop/cli/execute_module.py
Line 44 in 541dcd4
mesop run foo.py
Basically create a while loop which checks file modified time.
last_modified = os.path.getmtime(module_path)
while True:
# Get the current modification time
current_modified = os.path.getmtime(module_path)
# Compare the current modification time with the last modification time
if current_modified != last_modified:
# Update the last modification time
last_modified = current_modified
module = read_module(module_path)
if module is not None:
try:
runtime.set_module(module)
runtime.reload_all_sessions()
except Exception as e:
runtime.log(
pb.ServerLog(
message=f"Hot reload error: Module '{module_path}' has {e}.\n${traceback.format_exc()}",
level=pb.ServerLog.ERROR,
)
)
# Wait for 1 second before checking again
await asyncio.sleep(1)
Goals:
How:
DevToolsApps
app_with_dev_tools
)Steps:
-b
Conceptual docs
Reference docs
Since protos are serializable, we should support them as part of the state API
One of the challenges with developing Mesop applications for Python engineers with limited FE experience is that creating layouts and dealing with styling has a substantive learning curve.
By providing a visual editor, we can lower the barrier to entry and improve the velocity of Mesop app developers.
There's many similar attempts, but perhaps the most relevant example is Puck which is an open-source visual editor for React; it provides a very intuitive user experience. One of the differences, though, is that Puck emits JSON (a data structure of the component tree), but I'd like to emit Python code to close the gap between code produced by the visual editor vs. hand-written.
// Put in RenderEvent
message ComponentConfig {
string component_name
repeated EditorField fields
string category
}
message EditorField {
string name
ParamType type
}
message ParamType {
oneof type {
BoolType bool
IntType int
StringType string
StringLiteralType string_literal
ListType list
StructType struct
}
}
message BoolType {
bool default_value
}
message StructType {
repeated EditorField fields
}
message IntType {
int32 default_value
}
message StringType {
string default_value
}
message StringLiteralType {
repeated string literals // note: defaults to first element
}
message ListType {
ParamType type
}
message SourceCodeLocation {
string module
int32 line
int32 col
}
message ComponentEdit {
SourceCodeLocation location
string keyword_argument (can be empty, if it's a positional argument)
string new_value
}
Example program:
import inspect
from typing import Callable, Any, Literal
# The button function (for reference)
def button(
*,
on_click: Callable[[ClickEvent], Any] | None = None,
type: Literal["raised", "flat", "stroked", "icon"] | None = None,
color: str = "",
disable_ripple: bool = False,
disabled: bool = False,
key: str | None = None,
):
pass
# Function to analyze and serialize the function signature
def serialize_function_to_proto(func):
sig = inspect.signature(func)
component_config = {
"component_name": func.__name__,
"fields": [],
"category": "UI Components" # Example category
}
for name, param in sig.parameters.items():
editor_field = {"name": name}
param_type = param.annotation
# Map Python types to proto ParamType
if param_type in [bool]:
editor_field["type"] = {"bool": {"default_value": param.default}}
elif param_type in [str]:
editor_field["type"] = {"string": {"default_value": param.default}}
elif param_type == Literal["raised", "flat", "stroked", "icon"]:
editor_field["type"] = {"string_literal": {"literals": ["raised", "flat", "stroked", "icon"]}}
# Add more type mappings as needed...
component_config["fields"].append(editor_field)
return component_config
# Serialize the button function
serialized_button = serialize_function_to_proto(button)
print(serialized_button)
When you drag a component, it will create a placeholder component and emit a user event. EditorEvent. Inside EditorEvent, there's three types: 1) create, 2) edit, and 3) delete. After the user event, we will do a hot reload.
--prod
) which determines which binary to use.ComponentLibrary
component for left sidenav and ComponentFieldsEditor
and ComponentOutline
components for right sidenavinspect
and get the call-site; the frame before mesop/components)ComponentConfig
as part of RenderEvent in editor mode.Nice to haves:
I'd like to keep composite components as regular Python functions, but then it's difficult to display them in dev tools. In addition, it may be useful way to support content projection for these composite components as this provides a more flexible way of composing components together.
Sometimes you may want to fetch data or anything expensive / async for the initial screen.
There's no good way to do this in Mesop today, so you'd need to either a) wait until the user interacts (e.g. clicks a button) or b) do it as part of a navigation from a previous page.
Considerations
This is focused on allowing app developers to create their own custom components which are built on top of other components.
We will track in a separate issue the work to support custom native components.
Right now it hits an error because of an unrecognized flag -f
which is a file argument when executing a notebook.
f
flag<img>
<video>
<audio>
<a>
Linkhttps://material.angular.io/components/chips/overview
The loading indicator should always be visible (regardless if the viewport has been scrolled) and it should not bump the content. It should probably be done with a position: absolute
(but I'm not sure if we should create some margin, otherwise it will make it hard to style that area.
Currently when placeholder is specified, the textarea is blank initially until being clicked.
Goal: Provide hot reloading for developers using Optic.
Desired behavior: When an Optic application is modified under development mode, we will send a signal from the server to client to save the client state to a persistent web storage (e.g localStorage) and trigger a reload.
ibazel (https://github.com/bazelbuild/bazel-watcher) provides a helpful wrapper around bazel which watches for filesystem changes and triggers a livereload.
cli.py
, detect whether IBAZEL_LIVERELOAD_URL
environmental variable is present and use it to inject a script in index.html.Potential implementation:
def monitor_stdin():
while True:
line = sys.stdin.readline().strip()
if line == "SOURCE_CHANGE":
reexecute()
stdin_thread = threading.Thread(target=monitor_stdin)
stdin_thread.daemon = True
stdin_thread.start()
tags = [
# This tag starts the live_reload server inside iBazel and instructs it to send reload events to webbrowsers.
"ibazel_live_reload",
# This tag instructs ibazel to pipe into stdin a event describing actions.
"ibazel_notify_changes",
],
Right now I refer to the concept of a component which accepts a child as a Composite Component in the docs, but I've named the variations of components as content_{component}
(e.g. content_checkbox
, content_button
).
For consistency, this should always be referred to as Content Component.
Repro: Have a text input, if the user puts in a large text input, then Mesop throws an error 413 (Payload Too Large)
.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/413
These files use toObject
which is not a well-supported API for JS protos:
Alternatively, we can serialize (in development mode) the proto on the server-side using the Python proto API:
json_string = json_format.MessageToJson(message)
Diff:
Common case:
Right now, I've documented a set of steps to deploy Optic on Google Cloud Run, however it's a bit tedious to do this manually. If we can automate this, then: 1) we can ensure the steps don't get out of date and 2) we can deploy a demo site continuously via CI and either link or embed it from the docs.
If it's not a lot of work, we can do this as part of MVP, otherwise we can defer it.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.