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.
Files changed (152) hide show
  1. instaui/__init__.py +9 -0
  2. instaui/_helper/observable_helper.py +35 -0
  3. instaui/boot_info.py +43 -0
  4. instaui/common/jsonable.py +37 -0
  5. instaui/components/__init__.py +0 -0
  6. instaui/components/column.py +18 -0
  7. instaui/components/component.py +47 -0
  8. instaui/components/content.py +34 -0
  9. instaui/components/directive.py +55 -0
  10. instaui/components/element.py +462 -0
  11. instaui/components/grid.py +80 -0
  12. instaui/components/html/__init__.py +36 -0
  13. instaui/components/html/_mixins.py +34 -0
  14. instaui/components/html/button.py +38 -0
  15. instaui/components/html/checkbox.py +42 -0
  16. instaui/components/html/date.py +28 -0
  17. instaui/components/html/div.py +7 -0
  18. instaui/components/html/form.py +7 -0
  19. instaui/components/html/input.py +28 -0
  20. instaui/components/html/label.py +21 -0
  21. instaui/components/html/li.py +17 -0
  22. instaui/components/html/link.py +31 -0
  23. instaui/components/html/number.py +34 -0
  24. instaui/components/html/paragraph.py +19 -0
  25. instaui/components/html/range.py +45 -0
  26. instaui/components/html/select.py +93 -0
  27. instaui/components/html/span.py +19 -0
  28. instaui/components/html/ul.py +20 -0
  29. instaui/components/match.py +106 -0
  30. instaui/components/row.py +19 -0
  31. instaui/components/slot.py +82 -0
  32. instaui/components/transition_group.py +9 -0
  33. instaui/components/value_element.py +48 -0
  34. instaui/components/vfor.py +140 -0
  35. instaui/components/vif.py +38 -0
  36. instaui/consts.py +18 -0
  37. instaui/dependencies/__init__.py +15 -0
  38. instaui/dependencies/component_registrar.py +82 -0
  39. instaui/dependencies/installer.py +5 -0
  40. instaui/event/event_mixin.py +12 -0
  41. instaui/event/js_event.py +57 -0
  42. instaui/event/web_event.py +108 -0
  43. instaui/experimental/__init__.py +4 -0
  44. instaui/experimental/debug.py +48 -0
  45. instaui/fastapi_server/_utils.py +42 -0
  46. instaui/fastapi_server/_uvicorn.py +37 -0
  47. instaui/fastapi_server/config_router.py +60 -0
  48. instaui/fastapi_server/debug_mode_router.py +61 -0
  49. instaui/fastapi_server/event_router.py +58 -0
  50. instaui/fastapi_server/middlewares.py +19 -0
  51. instaui/fastapi_server/request_context.py +19 -0
  52. instaui/fastapi_server/server.py +246 -0
  53. instaui/fastapi_server/watch_router.py +53 -0
  54. instaui/handlers/_utils.py +66 -0
  55. instaui/handlers/computed_handler.py +42 -0
  56. instaui/handlers/config_handler.py +13 -0
  57. instaui/handlers/event_handler.py +58 -0
  58. instaui/handlers/watch_handler.py +57 -0
  59. instaui/html_tools.py +139 -0
  60. instaui/inject.py +33 -0
  61. instaui/js/__init__.py +4 -0
  62. instaui/js/js_output.py +15 -0
  63. instaui/js/lambda_func.py +35 -0
  64. instaui/launch_collector.py +52 -0
  65. instaui/page_info.py +23 -0
  66. instaui/runtime/__init__.py +29 -0
  67. instaui/runtime/_app.py +206 -0
  68. instaui/runtime/_inner_helper.py +9 -0
  69. instaui/runtime/context.py +47 -0
  70. instaui/runtime/dataclass.py +30 -0
  71. instaui/runtime/resource.py +87 -0
  72. instaui/runtime/scope.py +107 -0
  73. instaui/runtime/ui_state_scope.py +15 -0
  74. instaui/settings/__init__.py +4 -0
  75. instaui/settings/__settings.py +13 -0
  76. instaui/skip.py +12 -0
  77. instaui/spa_router/__init__.py +26 -0
  78. instaui/spa_router/_components.py +35 -0
  79. instaui/spa_router/_file_base_utils.py +264 -0
  80. instaui/spa_router/_functions.py +122 -0
  81. instaui/spa_router/_install.py +11 -0
  82. instaui/spa_router/_route_model.py +139 -0
  83. instaui/spa_router/_router_box.py +40 -0
  84. instaui/spa_router/_router_output.py +22 -0
  85. instaui/spa_router/_router_param_var.py +51 -0
  86. instaui/spa_router/_types.py +4 -0
  87. instaui/spa_router/templates/page_routes +59 -0
  88. instaui/static/insta-ui.css +1 -0
  89. instaui/static/insta-ui.esm-browser.prod.js +3663 -0
  90. instaui/static/insta-ui.iife.js +29 -0
  91. instaui/static/insta-ui.iife.js.map +1 -0
  92. instaui/static/insta-ui.js.map +1 -0
  93. instaui/static/tailwindcss.min.js +62 -0
  94. instaui/static/templates/debug/sse.html +117 -0
  95. instaui/static/templates/web.html +118 -0
  96. instaui/static/templates/zero.html +55 -0
  97. instaui/static/vue.esm-browser.prod.js +9 -0
  98. instaui/static/vue.global.prod.js +9 -0
  99. instaui/static/vue.runtime.esm-browser.prod.js +5 -0
  100. instaui/systems/file_system.py +17 -0
  101. instaui/systems/func_system.py +104 -0
  102. instaui/systems/js_system.py +22 -0
  103. instaui/systems/pydantic_system.py +27 -0
  104. instaui/systems/string_system.py +10 -0
  105. instaui/template/__init__.py +4 -0
  106. instaui/template/env.py +7 -0
  107. instaui/template/web_template.py +55 -0
  108. instaui/template/zero_template.py +24 -0
  109. instaui/ui/__init__.py +121 -0
  110. instaui/ui/events.py +25 -0
  111. instaui/ui_functions/input_slient_data.py +16 -0
  112. instaui/ui_functions/server.py +13 -0
  113. instaui/ui_functions/str_format.py +36 -0
  114. instaui/ui_functions/ui_page.py +31 -0
  115. instaui/ui_functions/ui_types.py +13 -0
  116. instaui/ui_functions/url_location.py +33 -0
  117. instaui/vars/__init__.py +13 -0
  118. instaui/vars/_types.py +8 -0
  119. instaui/vars/_utils.py +12 -0
  120. instaui/vars/data.py +68 -0
  121. instaui/vars/element_ref.py +42 -0
  122. instaui/vars/event_context.py +45 -0
  123. instaui/vars/event_extend.py +0 -0
  124. instaui/vars/js_computed.py +95 -0
  125. instaui/vars/mixin_types/common_type.py +5 -0
  126. instaui/vars/mixin_types/element_binding.py +10 -0
  127. instaui/vars/mixin_types/observable.py +7 -0
  128. instaui/vars/mixin_types/pathable.py +14 -0
  129. instaui/vars/mixin_types/py_binding.py +13 -0
  130. instaui/vars/mixin_types/str_format_binding.py +8 -0
  131. instaui/vars/mixin_types/var_type.py +5 -0
  132. instaui/vars/path_var.py +89 -0
  133. instaui/vars/ref.py +103 -0
  134. instaui/vars/slot_prop.py +46 -0
  135. instaui/vars/state.py +82 -0
  136. instaui/vars/types.py +24 -0
  137. instaui/vars/vfor_item.py +204 -0
  138. instaui/vars/vue_computed.py +82 -0
  139. instaui/vars/web_computed.py +157 -0
  140. instaui/vars/web_view_computed.py +1 -0
  141. instaui/version.py +3 -0
  142. instaui/watch/_types.py +4 -0
  143. instaui/watch/_utils.py +3 -0
  144. instaui/watch/js_watch.py +74 -0
  145. instaui/watch/vue_watch.py +61 -0
  146. instaui/watch/web_watch.py +123 -0
  147. instaui/zero/__init__.py +3 -0
  148. instaui/zero/scope.py +9 -0
  149. instaui-0.1.0.dist-info/LICENSE +21 -0
  150. instaui-0.1.0.dist-info/METADATA +154 -0
  151. instaui-0.1.0.dist-info/RECORD +152 -0
  152. 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,5 @@
1
+ from typing import Callable
2
+
3
+
4
+ def install_component(install: Callable):
5
+ install()
@@ -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,4 @@
1
+ from .debug import list_all_bindables
2
+ from instaui.vars.state import state
3
+
4
+ __all__ = ["list_all_bindables", "state"]
@@ -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