instaui 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- instaui/__init__.py +9 -0
- instaui/_helper/observable_helper.py +35 -0
- instaui/boot_info.py +43 -0
- instaui/common/jsonable.py +37 -0
- instaui/components/__init__.py +0 -0
- instaui/components/column.py +18 -0
- instaui/components/component.py +47 -0
- instaui/components/content.py +34 -0
- instaui/components/directive.py +55 -0
- instaui/components/element.py +462 -0
- instaui/components/grid.py +80 -0
- instaui/components/html/__init__.py +36 -0
- instaui/components/html/_mixins.py +34 -0
- instaui/components/html/button.py +38 -0
- instaui/components/html/checkbox.py +42 -0
- instaui/components/html/date.py +28 -0
- instaui/components/html/div.py +7 -0
- instaui/components/html/form.py +7 -0
- instaui/components/html/input.py +28 -0
- instaui/components/html/label.py +21 -0
- instaui/components/html/li.py +17 -0
- instaui/components/html/link.py +31 -0
- instaui/components/html/number.py +34 -0
- instaui/components/html/paragraph.py +19 -0
- instaui/components/html/range.py +45 -0
- instaui/components/html/select.py +93 -0
- instaui/components/html/span.py +19 -0
- instaui/components/html/ul.py +20 -0
- instaui/components/match.py +106 -0
- instaui/components/row.py +19 -0
- instaui/components/slot.py +82 -0
- instaui/components/transition_group.py +9 -0
- instaui/components/value_element.py +48 -0
- instaui/components/vfor.py +140 -0
- instaui/components/vif.py +38 -0
- instaui/consts.py +18 -0
- instaui/dependencies/__init__.py +15 -0
- instaui/dependencies/component_registrar.py +82 -0
- instaui/dependencies/installer.py +5 -0
- instaui/event/event_mixin.py +12 -0
- instaui/event/js_event.py +57 -0
- instaui/event/web_event.py +108 -0
- instaui/experimental/__init__.py +4 -0
- instaui/experimental/debug.py +48 -0
- instaui/fastapi_server/_utils.py +42 -0
- instaui/fastapi_server/_uvicorn.py +37 -0
- instaui/fastapi_server/config_router.py +60 -0
- instaui/fastapi_server/debug_mode_router.py +61 -0
- instaui/fastapi_server/event_router.py +58 -0
- instaui/fastapi_server/middlewares.py +19 -0
- instaui/fastapi_server/request_context.py +19 -0
- instaui/fastapi_server/server.py +246 -0
- instaui/fastapi_server/watch_router.py +53 -0
- instaui/handlers/_utils.py +66 -0
- instaui/handlers/computed_handler.py +42 -0
- instaui/handlers/config_handler.py +13 -0
- instaui/handlers/event_handler.py +58 -0
- instaui/handlers/watch_handler.py +57 -0
- instaui/html_tools.py +139 -0
- instaui/inject.py +33 -0
- instaui/js/__init__.py +4 -0
- instaui/js/js_output.py +15 -0
- instaui/js/lambda_func.py +35 -0
- instaui/launch_collector.py +52 -0
- instaui/page_info.py +23 -0
- instaui/runtime/__init__.py +29 -0
- instaui/runtime/_app.py +206 -0
- instaui/runtime/_inner_helper.py +9 -0
- instaui/runtime/context.py +47 -0
- instaui/runtime/dataclass.py +30 -0
- instaui/runtime/resource.py +87 -0
- instaui/runtime/scope.py +107 -0
- instaui/runtime/ui_state_scope.py +15 -0
- instaui/settings/__init__.py +4 -0
- instaui/settings/__settings.py +13 -0
- instaui/skip.py +12 -0
- instaui/spa_router/__init__.py +26 -0
- instaui/spa_router/_components.py +35 -0
- instaui/spa_router/_file_base_utils.py +264 -0
- instaui/spa_router/_functions.py +122 -0
- instaui/spa_router/_install.py +11 -0
- instaui/spa_router/_route_model.py +139 -0
- instaui/spa_router/_router_box.py +40 -0
- instaui/spa_router/_router_output.py +22 -0
- instaui/spa_router/_router_param_var.py +51 -0
- instaui/spa_router/_types.py +4 -0
- instaui/spa_router/templates/page_routes +59 -0
- instaui/static/insta-ui.css +1 -0
- instaui/static/insta-ui.esm-browser.prod.js +3663 -0
- instaui/static/insta-ui.iife.js +29 -0
- instaui/static/insta-ui.iife.js.map +1 -0
- instaui/static/insta-ui.js.map +1 -0
- instaui/static/tailwindcss.min.js +62 -0
- instaui/static/templates/debug/sse.html +117 -0
- instaui/static/templates/web.html +118 -0
- instaui/static/templates/zero.html +55 -0
- instaui/static/vue.esm-browser.prod.js +9 -0
- instaui/static/vue.global.prod.js +9 -0
- instaui/static/vue.runtime.esm-browser.prod.js +5 -0
- instaui/systems/file_system.py +17 -0
- instaui/systems/func_system.py +104 -0
- instaui/systems/js_system.py +22 -0
- instaui/systems/pydantic_system.py +27 -0
- instaui/systems/string_system.py +10 -0
- instaui/template/__init__.py +4 -0
- instaui/template/env.py +7 -0
- instaui/template/web_template.py +55 -0
- instaui/template/zero_template.py +24 -0
- instaui/ui/__init__.py +121 -0
- instaui/ui/events.py +25 -0
- instaui/ui_functions/input_slient_data.py +16 -0
- instaui/ui_functions/server.py +13 -0
- instaui/ui_functions/str_format.py +36 -0
- instaui/ui_functions/ui_page.py +31 -0
- instaui/ui_functions/ui_types.py +13 -0
- instaui/ui_functions/url_location.py +33 -0
- instaui/vars/__init__.py +13 -0
- instaui/vars/_types.py +8 -0
- instaui/vars/_utils.py +12 -0
- instaui/vars/data.py +68 -0
- instaui/vars/element_ref.py +42 -0
- instaui/vars/event_context.py +45 -0
- instaui/vars/event_extend.py +0 -0
- instaui/vars/js_computed.py +95 -0
- instaui/vars/mixin_types/common_type.py +5 -0
- instaui/vars/mixin_types/element_binding.py +10 -0
- instaui/vars/mixin_types/observable.py +7 -0
- instaui/vars/mixin_types/pathable.py +14 -0
- instaui/vars/mixin_types/py_binding.py +13 -0
- instaui/vars/mixin_types/str_format_binding.py +8 -0
- instaui/vars/mixin_types/var_type.py +5 -0
- instaui/vars/path_var.py +89 -0
- instaui/vars/ref.py +103 -0
- instaui/vars/slot_prop.py +46 -0
- instaui/vars/state.py +82 -0
- instaui/vars/types.py +24 -0
- instaui/vars/vfor_item.py +204 -0
- instaui/vars/vue_computed.py +82 -0
- instaui/vars/web_computed.py +157 -0
- instaui/vars/web_view_computed.py +1 -0
- instaui/version.py +3 -0
- instaui/watch/_types.py +4 -0
- instaui/watch/_utils.py +3 -0
- instaui/watch/js_watch.py +74 -0
- instaui/watch/vue_watch.py +61 -0
- instaui/watch/web_watch.py +123 -0
- instaui/zero/__init__.py +3 -0
- instaui/zero/scope.py +9 -0
- instaui-0.1.0.dist-info/LICENSE +21 -0
- instaui-0.1.0.dist-info/METADATA +154 -0
- instaui-0.1.0.dist-info/RECORD +152 -0
- instaui-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,140 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import (
|
3
|
+
Dict,
|
4
|
+
List,
|
5
|
+
Literal,
|
6
|
+
Mapping,
|
7
|
+
Optional,
|
8
|
+
Union,
|
9
|
+
Sequence,
|
10
|
+
Generic,
|
11
|
+
TypeVar,
|
12
|
+
overload,
|
13
|
+
)
|
14
|
+
import pydantic
|
15
|
+
|
16
|
+
from instaui.components.component import Component
|
17
|
+
from instaui.vars.vfor_item import VForItem, VForDict, VForWithIndex
|
18
|
+
from instaui.runtime._app import get_app_slot, new_scope
|
19
|
+
|
20
|
+
from instaui.vars.mixin_types.element_binding import ElementBindingMixin
|
21
|
+
|
22
|
+
_T = TypeVar("_T")
|
23
|
+
|
24
|
+
|
25
|
+
class VFor(Component, Generic[_T]):
|
26
|
+
def __init__(
|
27
|
+
self,
|
28
|
+
data: Union[Sequence[_T], ElementBindingMixin[List[_T]]],
|
29
|
+
*,
|
30
|
+
key: Union[Literal["item", "index"], str] = "index",
|
31
|
+
):
|
32
|
+
"""for loop component.
|
33
|
+
|
34
|
+
Args:
|
35
|
+
data (Union[Sequence[_T], ElementBindingMixin[List[_T]]]): data source.
|
36
|
+
key (Union[Literal["item", "index"], str]]): key for each item. Defaults to 'index'.
|
37
|
+
|
38
|
+
Examples:
|
39
|
+
.. code-block:: python
|
40
|
+
items = ui.state([1,2,3])
|
41
|
+
|
42
|
+
with ui.vfor(items) as item:
|
43
|
+
html.span(item)
|
44
|
+
|
45
|
+
# object key
|
46
|
+
items = ui.state([{"name": "Alice", "age": 20}, {"name": "Bob", "age": 30}])
|
47
|
+
with ui.vfor(items, key=":item=>item.name") as item:
|
48
|
+
html.span(item.name)
|
49
|
+
"""
|
50
|
+
|
51
|
+
super().__init__("vfor")
|
52
|
+
self._data = data
|
53
|
+
self._key = key
|
54
|
+
self._fid = get_app_slot().generate_vfor_id()
|
55
|
+
self.__scope_manager = new_scope(append_to_app=False)
|
56
|
+
self.__scope = None
|
57
|
+
self._num = None
|
58
|
+
self._transition_group_setting = None
|
59
|
+
|
60
|
+
def __enter__(self) -> _T:
|
61
|
+
self.__scope = self.__scope_manager.__enter__()
|
62
|
+
super().__enter__()
|
63
|
+
return VForItem(self).proxy
|
64
|
+
|
65
|
+
def __exit__(self, *_) -> None:
|
66
|
+
self.__scope_manager.__exit__(*_)
|
67
|
+
return super().__exit__(*_)
|
68
|
+
|
69
|
+
def _set_num(self, num):
|
70
|
+
self._num = num
|
71
|
+
|
72
|
+
def transition_group(self, name="fade", tag: Optional[str] = None):
|
73
|
+
self._transition_group_setting = {"name": name, "tag": tag}
|
74
|
+
return self
|
75
|
+
|
76
|
+
@property
|
77
|
+
def current(self):
|
78
|
+
return VForItem(self)
|
79
|
+
|
80
|
+
def with_index(self):
|
81
|
+
return VForWithIndex(self)
|
82
|
+
|
83
|
+
def _to_json_dict(self):
|
84
|
+
data = super()._to_json_dict()
|
85
|
+
data["props"] = {"fid": self._fid}
|
86
|
+
|
87
|
+
props: Dict = data["props"]
|
88
|
+
if self._key is not None and self._key != "index":
|
89
|
+
props["fkey"] = self._key
|
90
|
+
|
91
|
+
if self._data is not None:
|
92
|
+
if isinstance(self._data, ElementBindingMixin):
|
93
|
+
props["bArray"] = self._data._to_element_binding_config()
|
94
|
+
else:
|
95
|
+
props["array"] = self._data
|
96
|
+
|
97
|
+
if self._num is not None:
|
98
|
+
props["num"] = self._num
|
99
|
+
|
100
|
+
if self._transition_group_setting is not None:
|
101
|
+
props["tsGroup"] = {
|
102
|
+
k: v for k, v in self._transition_group_setting.items() if v is not None
|
103
|
+
}
|
104
|
+
|
105
|
+
props["scope"] = self.__scope
|
106
|
+
|
107
|
+
if self._slot_manager.has_slot():
|
108
|
+
props["items"] = self._slot_manager
|
109
|
+
|
110
|
+
data.pop("slots", None)
|
111
|
+
|
112
|
+
return data
|
113
|
+
|
114
|
+
@overload
|
115
|
+
@classmethod
|
116
|
+
def range(cls, end: int) -> VFor[int]: ...
|
117
|
+
|
118
|
+
@overload
|
119
|
+
@classmethod
|
120
|
+
def range(cls, end: ElementBindingMixin[int]) -> VFor[int]: ...
|
121
|
+
|
122
|
+
@classmethod
|
123
|
+
def range(cls, end: Union[int, ElementBindingMixin[int]]) -> VFor[int]:
|
124
|
+
obj = cls(None) # type: ignore
|
125
|
+
|
126
|
+
num = ( # type: ignore
|
127
|
+
end._to_element_binding_config()
|
128
|
+
if isinstance(end, ElementBindingMixin)
|
129
|
+
else end
|
130
|
+
)
|
131
|
+
|
132
|
+
obj._set_num(num)
|
133
|
+
|
134
|
+
return obj # type: ignore
|
135
|
+
|
136
|
+
@classmethod
|
137
|
+
def from_dict(
|
138
|
+
cls, data: Union[Mapping, pydantic.BaseModel, ElementBindingMixin[Dict]]
|
139
|
+
):
|
140
|
+
return VForDict(VFor(data)) # type: ignore
|
@@ -0,0 +1,38 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Dict, cast
|
3
|
+
from instaui.components.component import Component
|
4
|
+
from instaui.runtime._app import new_scope
|
5
|
+
from instaui.vars.mixin_types.element_binding import ElementBindingMixin
|
6
|
+
from instaui.vars.types import TMaybeRef
|
7
|
+
|
8
|
+
|
9
|
+
class VIf(Component):
|
10
|
+
def __init__(self, on: TMaybeRef[bool]):
|
11
|
+
super().__init__("vif")
|
12
|
+
self._on = cast(ElementBindingMixin, on)
|
13
|
+
self.__scope_manager = new_scope(append_to_app=False)
|
14
|
+
self.__scope = None
|
15
|
+
|
16
|
+
def __enter__(self):
|
17
|
+
self.__scope = self.__scope_manager.__enter__()
|
18
|
+
return super().__enter__()
|
19
|
+
|
20
|
+
def __exit__(self, *_) -> None:
|
21
|
+
self.__scope_manager.__exit__(*_)
|
22
|
+
return super().__exit__(*_)
|
23
|
+
|
24
|
+
def _to_json_dict(self):
|
25
|
+
data = super()._to_json_dict()
|
26
|
+
data["props"] = {
|
27
|
+
"on": self._on._to_element_binding_config(),
|
28
|
+
}
|
29
|
+
props: Dict = data["props"]
|
30
|
+
|
31
|
+
props["scope"] = self.__scope
|
32
|
+
|
33
|
+
if self._slot_manager.has_slot():
|
34
|
+
props["items"] = self._slot_manager
|
35
|
+
|
36
|
+
data.pop("slots", None)
|
37
|
+
|
38
|
+
return data
|
instaui/consts.py
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from typing import Literal
|
3
|
+
|
4
|
+
_THIS_DIR = Path(__file__).parent
|
5
|
+
_TEMPLATES_DIR = _THIS_DIR.joinpath("templates")
|
6
|
+
_STATIC_DIR = _THIS_DIR.joinpath("static")
|
7
|
+
|
8
|
+
INDEX_TEMPLATE_PATH = _TEMPLATES_DIR.joinpath("index.html")
|
9
|
+
APP_IIFE_JS_PATH = _STATIC_DIR.joinpath("insta-ui.iife.js")
|
10
|
+
APP_ES_JS_PATH = _STATIC_DIR.joinpath("insta-ui.esm-browser.prod.js")
|
11
|
+
APP_CSS_PATH = _STATIC_DIR.joinpath("insta-ui.css")
|
12
|
+
VUE_IIFE_JS_PATH = _STATIC_DIR.joinpath("vue.global.prod.js")
|
13
|
+
VUE_ES_JS_PATH = _STATIC_DIR.joinpath("vue.esm-browser.prod.js")
|
14
|
+
VUE_ES_RUNTIME_JS_PATH = _STATIC_DIR.joinpath("vue.runtime.esm-browser.prod.js")
|
15
|
+
TAILWIND_JS_PATH = _STATIC_DIR.joinpath("tailwindcss.min.js")
|
16
|
+
|
17
|
+
_T_App_Mode = Literal["zero", "web", "webview"]
|
18
|
+
TModifier = Literal["trim", "number", "lazy"]
|
@@ -0,0 +1,15 @@
|
|
1
|
+
from .component_registrar import (
|
2
|
+
ComponentRegistrationInfo,
|
3
|
+
register_component,
|
4
|
+
PluginRegistrationInfo,
|
5
|
+
register_plugin,
|
6
|
+
)
|
7
|
+
from .installer import install_component
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"ComponentRegistrationInfo",
|
11
|
+
"register_component",
|
12
|
+
"PluginRegistrationInfo",
|
13
|
+
"register_plugin",
|
14
|
+
"install_component",
|
15
|
+
]
|
@@ -0,0 +1,82 @@
|
|
1
|
+
import inspect
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import List, Optional, Type, Union
|
4
|
+
from instaui.systems.file_system import generate_hash_name_from_path
|
5
|
+
from dataclasses import dataclass, field
|
6
|
+
from instaui.runtime import get_app_slot
|
7
|
+
from instaui.runtime._app import App
|
8
|
+
|
9
|
+
|
10
|
+
class ComponentRegistrar:
|
11
|
+
def __init__(self, js_file: Path):
|
12
|
+
self.js_file = js_file
|
13
|
+
self.key = f"{generate_hash_name_from_path(js_file.parent)}/{js_file.name}"
|
14
|
+
self.name = js_file.stem
|
15
|
+
|
16
|
+
def __hash__(self) -> int:
|
17
|
+
return hash(self.js_file)
|
18
|
+
|
19
|
+
def __eq__(self, other: object) -> bool:
|
20
|
+
if not isinstance(other, ComponentRegistrar):
|
21
|
+
return False
|
22
|
+
return self.js_file == other.js_file
|
23
|
+
|
24
|
+
@classmethod
|
25
|
+
def create(cls, js_file: Union[str, Path], target_class: Type):
|
26
|
+
js_file = Path(js_file)
|
27
|
+
base = Path(inspect.getfile(target_class)).parent
|
28
|
+
if not js_file.is_absolute():
|
29
|
+
js_file = base / js_file
|
30
|
+
return cls(js_file)
|
31
|
+
|
32
|
+
|
33
|
+
@dataclass(frozen=True)
|
34
|
+
class ComponentRegistrationInfo:
|
35
|
+
name: str
|
36
|
+
esm: Optional[Path] = None
|
37
|
+
iife: Optional[Path] = None
|
38
|
+
css: List[Path] = field(default_factory=list, compare=False)
|
39
|
+
|
40
|
+
|
41
|
+
@dataclass(frozen=True)
|
42
|
+
class PluginRegistrationInfo:
|
43
|
+
name: str
|
44
|
+
esm: Optional[Path] = None
|
45
|
+
iife: Optional[Path] = None
|
46
|
+
css: List[Path] = field(default_factory=list, compare=False)
|
47
|
+
|
48
|
+
|
49
|
+
def register_plugin(
|
50
|
+
name: str,
|
51
|
+
esm: Optional[Path] = None,
|
52
|
+
iife: Optional[Path] = None,
|
53
|
+
css: Optional[Union[Path, List[Path]]] = None,
|
54
|
+
shared: bool = False,
|
55
|
+
):
|
56
|
+
css = [css] if isinstance(css, Path) else css
|
57
|
+
|
58
|
+
cr = PluginRegistrationInfo(name, esm, iife, css or [])
|
59
|
+
|
60
|
+
if shared:
|
61
|
+
App.default_plugins(cr)
|
62
|
+
|
63
|
+
get_app_slot().register_plugin(cr)
|
64
|
+
return cr
|
65
|
+
|
66
|
+
|
67
|
+
def register_component(
|
68
|
+
name: str,
|
69
|
+
esm: Optional[Path] = None,
|
70
|
+
iife: Optional[Path] = None,
|
71
|
+
css: Optional[Union[Path, List[Path]]] = None,
|
72
|
+
shared: bool = False,
|
73
|
+
):
|
74
|
+
css = [css] if isinstance(css, Path) else css
|
75
|
+
|
76
|
+
cr = ComponentRegistrationInfo(f"instaui-{name}", esm, iife, css or [])
|
77
|
+
|
78
|
+
if shared:
|
79
|
+
App.default_js_components(cr)
|
80
|
+
|
81
|
+
get_app_slot().register_component(cr)
|
82
|
+
return cr
|
@@ -0,0 +1,12 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
import typing
|
3
|
+
|
4
|
+
|
5
|
+
class EventMixin(ABC):
|
6
|
+
@abstractmethod
|
7
|
+
def copy_with_extends(self, extends: typing.Sequence) -> "EventMixin":
|
8
|
+
pass
|
9
|
+
|
10
|
+
@abstractmethod
|
11
|
+
def event_type(self) -> typing.Literal["web", "js"]:
|
12
|
+
pass
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import typing
|
2
|
+
from instaui.vars.mixin_types.py_binding import CanInputMixin, CanOutputMixin
|
3
|
+
from instaui.common.jsonable import Jsonable
|
4
|
+
from .event_mixin import EventMixin
|
5
|
+
|
6
|
+
|
7
|
+
class JsEvent(Jsonable, EventMixin):
|
8
|
+
def __init__(
|
9
|
+
self,
|
10
|
+
code: str,
|
11
|
+
inputs: typing.Optional[typing.Sequence[CanInputMixin]] = None,
|
12
|
+
outputs: typing.Optional[typing.Sequence[CanOutputMixin]] = None,
|
13
|
+
):
|
14
|
+
self._is_const_data = [
|
15
|
+
int(not isinstance(input, CanInputMixin)) for input in inputs or []
|
16
|
+
]
|
17
|
+
self._org_inputs = list(inputs or [])
|
18
|
+
self._org_outputs = list(outputs or [])
|
19
|
+
self._inputs = [
|
20
|
+
input._to_input_config() if isinstance(input, CanInputMixin) else input
|
21
|
+
for input in inputs or []
|
22
|
+
]
|
23
|
+
self._outputs = [output._to_output_config() for output in outputs or []]
|
24
|
+
self.code = code
|
25
|
+
|
26
|
+
def _to_json_dict(self):
|
27
|
+
data = super()._to_json_dict()
|
28
|
+
|
29
|
+
if self._inputs:
|
30
|
+
data["inputs"] = self._inputs
|
31
|
+
|
32
|
+
if self._outputs:
|
33
|
+
data["set"] = self._outputs
|
34
|
+
|
35
|
+
if sum(self._is_const_data) > 0:
|
36
|
+
data["data"] = self._is_const_data
|
37
|
+
|
38
|
+
return data
|
39
|
+
|
40
|
+
def copy_with_extends(self, extends: typing.Sequence):
|
41
|
+
return js_event(
|
42
|
+
code=self.code,
|
43
|
+
inputs=self._org_inputs + list(extends),
|
44
|
+
outputs=self._org_outputs,
|
45
|
+
)
|
46
|
+
|
47
|
+
def event_type(self):
|
48
|
+
return "js"
|
49
|
+
|
50
|
+
|
51
|
+
def js_event(
|
52
|
+
*,
|
53
|
+
inputs: typing.Optional[typing.Sequence] = None,
|
54
|
+
outputs: typing.Optional[typing.Sequence] = None,
|
55
|
+
code: str,
|
56
|
+
):
|
57
|
+
return JsEvent(inputs=inputs, outputs=outputs, code=code)
|
@@ -0,0 +1,108 @@
|
|
1
|
+
import inspect
|
2
|
+
import typing
|
3
|
+
from typing_extensions import ParamSpec
|
4
|
+
from instaui.common.jsonable import Jsonable
|
5
|
+
from instaui.runtime._app import get_current_scope, get_app_slot
|
6
|
+
from instaui.vars.mixin_types.py_binding import CanInputMixin, CanOutputMixin
|
7
|
+
from instaui.handlers import event_handler
|
8
|
+
from .event_mixin import EventMixin
|
9
|
+
|
10
|
+
|
11
|
+
P = ParamSpec("P")
|
12
|
+
R = typing.TypeVar("R")
|
13
|
+
_T_input = typing.TypeVar("_T_input")
|
14
|
+
_T_output = typing.TypeVar("_T_output")
|
15
|
+
|
16
|
+
|
17
|
+
class WebEvent(Jsonable, EventMixin, typing.Generic[P, R]):
|
18
|
+
def __init__(
|
19
|
+
self,
|
20
|
+
fn: typing.Callable[P, R],
|
21
|
+
inputs: typing.List[CanInputMixin],
|
22
|
+
outputs: typing.List[CanOutputMixin],
|
23
|
+
):
|
24
|
+
self._inputs = inputs
|
25
|
+
self._outputs = outputs
|
26
|
+
self._fn = fn
|
27
|
+
|
28
|
+
scope = get_current_scope()
|
29
|
+
self._sid = scope.id
|
30
|
+
|
31
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
32
|
+
return self._fn(*args, **kwargs)
|
33
|
+
|
34
|
+
def copy_with_extends(self, extends: typing.Sequence[CanInputMixin]):
|
35
|
+
return WebEvent(
|
36
|
+
fn=self._fn,
|
37
|
+
inputs=self._inputs + list(extends),
|
38
|
+
outputs=self._outputs,
|
39
|
+
)
|
40
|
+
|
41
|
+
def event_type(self):
|
42
|
+
return "web"
|
43
|
+
|
44
|
+
def _to_json_dict(self):
|
45
|
+
app = get_app_slot()
|
46
|
+
|
47
|
+
hkey = event_handler.create_handler_key(
|
48
|
+
page_path=app.page_path, handler=self._fn
|
49
|
+
)
|
50
|
+
|
51
|
+
handler_path = (
|
52
|
+
event_handler.ASYNC_URL
|
53
|
+
if inspect.iscoroutinefunction(self._fn)
|
54
|
+
else event_handler.SYNC_URL
|
55
|
+
)
|
56
|
+
|
57
|
+
event_handler.register_event_handler(
|
58
|
+
hkey, self._fn, self._outputs, self._inputs
|
59
|
+
)
|
60
|
+
|
61
|
+
data = {}
|
62
|
+
data["url"] = handler_path
|
63
|
+
data["hKey"] = hkey
|
64
|
+
data["sid"] = self._sid
|
65
|
+
|
66
|
+
if self._inputs:
|
67
|
+
data["bind"] = [
|
68
|
+
binding._to_input_config()
|
69
|
+
if isinstance(binding, CanInputMixin)
|
70
|
+
else binding
|
71
|
+
for binding in self._inputs
|
72
|
+
]
|
73
|
+
|
74
|
+
if self._outputs:
|
75
|
+
data["set"] = [ref._to_output_config() for ref in self._outputs]
|
76
|
+
|
77
|
+
return data
|
78
|
+
|
79
|
+
|
80
|
+
@typing.overload
|
81
|
+
def ui_event(fn: typing.Callable[P, R]) -> WebEvent[P, R]: ...
|
82
|
+
|
83
|
+
|
84
|
+
@typing.overload
|
85
|
+
def ui_event(
|
86
|
+
*,
|
87
|
+
inputs: typing.Optional[typing.Union[_T_input, typing.Sequence[_T_input]]] = None,
|
88
|
+
outputs: typing.Optional[
|
89
|
+
typing.Union[_T_output, typing.Sequence[_T_output]]
|
90
|
+
] = None,
|
91
|
+
) -> typing.Callable[[typing.Callable[P, R]], WebEvent[P, R]]: ...
|
92
|
+
|
93
|
+
|
94
|
+
def ui_event(
|
95
|
+
fn: typing.Optional[typing.Callable[P, R]] = None, *, inputs=None, outputs=None
|
96
|
+
) -> typing.Union[
|
97
|
+
WebEvent[P, R], typing.Callable[[typing.Callable[P, R]], WebEvent[P, R]]
|
98
|
+
]:
|
99
|
+
inputs = [inputs] if isinstance(inputs, CanInputMixin) else inputs
|
100
|
+
outputs = [outputs] if isinstance(outputs, CanOutputMixin) else outputs
|
101
|
+
if fn is None:
|
102
|
+
|
103
|
+
def wrapper(fn: typing.Callable[P, R]):
|
104
|
+
return WebEvent(fn, inputs=inputs or [], outputs=outputs or [])
|
105
|
+
|
106
|
+
return wrapper
|
107
|
+
|
108
|
+
return WebEvent(fn, inputs=inputs or [], outputs=outputs or [])
|
@@ -0,0 +1,48 @@
|
|
1
|
+
from typing import Dict
|
2
|
+
from instaui import ui
|
3
|
+
from instaui.components import html
|
4
|
+
from instaui.vars.mixin_types.observable import ObservableMixin
|
5
|
+
|
6
|
+
|
7
|
+
def list_all_bindables(locals_dict: Dict):
|
8
|
+
"""List all bindables in the locals() dictionary.
|
9
|
+
|
10
|
+
|
11
|
+
Example usage:
|
12
|
+
|
13
|
+
```python
|
14
|
+
list_all_bindables(locals())
|
15
|
+
```
|
16
|
+
|
17
|
+
"""
|
18
|
+
|
19
|
+
with html.div().style(
|
20
|
+
"display: grid; grid-template-columns: auto 1fr; border: 1px solid black; padding: 10px;"
|
21
|
+
):
|
22
|
+
html.label("variable name")
|
23
|
+
html.label("bindable value").style("justify-self: center;")
|
24
|
+
|
25
|
+
html.div().style(
|
26
|
+
"grid-column: 1 / span 2;height: 1px;border-bottom: 1px solid black;"
|
27
|
+
)
|
28
|
+
|
29
|
+
for key, value in locals_dict.items():
|
30
|
+
if isinstance(value, ObservableMixin):
|
31
|
+
cp_value = ui.js_computed(
|
32
|
+
inputs=[value],
|
33
|
+
code=r"""(obj)=>{
|
34
|
+
|
35
|
+
if (typeof obj === 'object') {
|
36
|
+
if (obj === null) {
|
37
|
+
return 'null';
|
38
|
+
} else {
|
39
|
+
return JSON.stringify(obj);
|
40
|
+
}
|
41
|
+
} else {
|
42
|
+
return String(obj);
|
43
|
+
}
|
44
|
+
}""",
|
45
|
+
)
|
46
|
+
|
47
|
+
html.paragraph(f"{key}:").style("justify-self: end;")
|
48
|
+
html.paragraph(cp_value).style("justify-self: center;")
|
@@ -0,0 +1,42 @@
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
2
|
+
from instaui.runtime._app import get_app_slot
|
3
|
+
from instaui.skip import is_skip_output
|
4
|
+
import pydantic
|
5
|
+
|
6
|
+
|
7
|
+
class ResponseData(pydantic.BaseModel):
|
8
|
+
values: Optional[List[Any]] = None
|
9
|
+
skips: Optional[List[int]] = None
|
10
|
+
|
11
|
+
|
12
|
+
def update_app_page_info(data: Dict):
|
13
|
+
app = get_app_slot()
|
14
|
+
|
15
|
+
page_info = data.get("page", {})
|
16
|
+
app._page_path = page_info["path"]
|
17
|
+
|
18
|
+
if "params" in page_info:
|
19
|
+
app._page_params = page_info["params"]
|
20
|
+
|
21
|
+
if "queryParams" in page_info:
|
22
|
+
app._query_params = page_info["queryParams"]
|
23
|
+
|
24
|
+
|
25
|
+
def response_data(outputs_binding_count: int, result: Any):
|
26
|
+
data = ResponseData()
|
27
|
+
if outputs_binding_count > 0:
|
28
|
+
if not isinstance(result, tuple):
|
29
|
+
result = [result]
|
30
|
+
|
31
|
+
result_infos = [(r, int(is_skip_output(r))) for r in result]
|
32
|
+
|
33
|
+
if len(result_infos) == 1 and result_infos[0][1] == 1:
|
34
|
+
return data
|
35
|
+
|
36
|
+
data.values = [0 if info[1] == 1 else info[0] for info in result_infos]
|
37
|
+
skips = [info[1] for info in result_infos]
|
38
|
+
|
39
|
+
if sum(skips) > 0:
|
40
|
+
data.skips = skips
|
41
|
+
|
42
|
+
return data
|
@@ -0,0 +1,37 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from types import FrameType
|
3
|
+
import typing
|
4
|
+
import uvicorn
|
5
|
+
import socket
|
6
|
+
|
7
|
+
|
8
|
+
ThandleExitCallbacks = typing.List[typing.Callable[[], typing.Any]]
|
9
|
+
|
10
|
+
|
11
|
+
class UvicornServer(uvicorn.Server):
|
12
|
+
instance: UvicornServer
|
13
|
+
|
14
|
+
def __init__(self, config: uvicorn.Config) -> None:
|
15
|
+
super().__init__(config)
|
16
|
+
self._handle_exit_callbacks: ThandleExitCallbacks = []
|
17
|
+
|
18
|
+
@classmethod
|
19
|
+
def create_singleton(
|
20
|
+
cls, config: uvicorn.Config, handle_exit_callbacks: ThandleExitCallbacks
|
21
|
+
) -> None:
|
22
|
+
cls.instance = cls(config=config)
|
23
|
+
for callback in handle_exit_callbacks:
|
24
|
+
cls.instance.on_handle_exit(callback)
|
25
|
+
|
26
|
+
def run(self, sockets: typing.Optional[typing.List[socket.socket]] = None) -> None:
|
27
|
+
self.instance = self
|
28
|
+
|
29
|
+
super().run()
|
30
|
+
|
31
|
+
def on_handle_exit(self, callback: typing.Callable[[], typing.Any]):
|
32
|
+
self._handle_exit_callbacks.append(callback)
|
33
|
+
|
34
|
+
def handle_exit(self, sig: int, frame: FrameType | None) -> None:
|
35
|
+
for callback in self._handle_exit_callbacks:
|
36
|
+
callback()
|
37
|
+
return super().handle_exit(sig, frame)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
from typing import Dict
|
2
|
+
from fastapi import FastAPI
|
3
|
+
from contextlib import contextmanager
|
4
|
+
from instaui.html_tools import to_config_data
|
5
|
+
from instaui.handlers import config_handler
|
6
|
+
from instaui.launch_collector import get_launch_collector
|
7
|
+
from . import _utils
|
8
|
+
|
9
|
+
|
10
|
+
def create_router(app: FastAPI):
|
11
|
+
_async_config_handler(app)
|
12
|
+
_sync_config_handler(app)
|
13
|
+
|
14
|
+
|
15
|
+
def _async_config_handler(app: FastAPI):
|
16
|
+
@app.post(config_handler.ASYNC_URL)
|
17
|
+
async def _(data: Dict):
|
18
|
+
key = data.get("key", None)
|
19
|
+
handler = config_handler.get_handler(key)
|
20
|
+
if handler is None:
|
21
|
+
return {"error": "event handler not found"}
|
22
|
+
|
23
|
+
_utils.update_app_page_info(data)
|
24
|
+
|
25
|
+
with _execute_request_lifespans():
|
26
|
+
await handler()
|
27
|
+
|
28
|
+
return to_config_data()
|
29
|
+
|
30
|
+
|
31
|
+
def _sync_config_handler(app: FastAPI):
|
32
|
+
@app.post(config_handler.SYNC_URL)
|
33
|
+
def _(data: Dict):
|
34
|
+
key = data.get("key", None)
|
35
|
+
|
36
|
+
handler = config_handler.get_handler(key)
|
37
|
+
if handler is None:
|
38
|
+
return {"error": "event handler not found"}
|
39
|
+
|
40
|
+
_utils.update_app_page_info(data)
|
41
|
+
|
42
|
+
with _execute_request_lifespans():
|
43
|
+
handler()
|
44
|
+
|
45
|
+
return to_config_data()
|
46
|
+
|
47
|
+
|
48
|
+
@contextmanager
|
49
|
+
def _execute_request_lifespans():
|
50
|
+
events = [iter(event()) for event in get_launch_collector().page_request_lifespans]
|
51
|
+
for event in events:
|
52
|
+
next(event)
|
53
|
+
|
54
|
+
yield
|
55
|
+
|
56
|
+
for event in events:
|
57
|
+
try:
|
58
|
+
next(event)
|
59
|
+
except StopIteration:
|
60
|
+
pass
|