instaui 0.1.15__py2.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 +45 -0
- instaui/arco/__init__.py +191 -0
- instaui/arco/_settings.py +25 -0
- instaui/arco/_use_tools/locale.py +50 -0
- instaui/arco/component_types.py +1019 -0
- instaui/arco/components/_utils.py +22 -0
- instaui/arco/components/affix.py +29 -0
- instaui/arco/components/alert.py +42 -0
- instaui/arco/components/anchor.py +42 -0
- instaui/arco/components/auto_complete.py +96 -0
- instaui/arco/components/avatar.py +55 -0
- instaui/arco/components/back_top.py +14 -0
- instaui/arco/components/badge.py +14 -0
- instaui/arco/components/breadcrumb.py +14 -0
- instaui/arco/components/button.py +43 -0
- instaui/arco/components/calendar.py +47 -0
- instaui/arco/components/card.py +14 -0
- instaui/arco/components/carousel.py +33 -0
- instaui/arco/components/cascader.py +111 -0
- instaui/arco/components/checkbox.py +32 -0
- instaui/arco/components/collapse.py +31 -0
- instaui/arco/components/color_picker.py +45 -0
- instaui/arco/components/comment.py +14 -0
- instaui/arco/components/config_provider.py +13 -0
- instaui/arco/components/date_picker.py +111 -0
- instaui/arco/components/descriptions.py +14 -0
- instaui/arco/components/divider.py +13 -0
- instaui/arco/components/drawer.py +98 -0
- instaui/arco/components/dropdown.py +45 -0
- instaui/arco/components/empty.py +14 -0
- instaui/arco/components/form.py +55 -0
- instaui/arco/components/icon.py +17 -0
- instaui/arco/components/image.py +33 -0
- instaui/arco/components/input.py +102 -0
- instaui/arco/components/input_number.py +97 -0
- instaui/arco/components/input_password.py +38 -0
- instaui/arco/components/input_search.py +37 -0
- instaui/arco/components/input_tag.py +110 -0
- instaui/arco/components/layout.py +13 -0
- instaui/arco/components/layout_content.py +6 -0
- instaui/arco/components/layout_footer.py +6 -0
- instaui/arco/components/layout_header.py +6 -0
- instaui/arco/components/layout_sider.py +53 -0
- instaui/arco/components/link.py +36 -0
- instaui/arco/components/list.py +68 -0
- instaui/arco/components/mention.py +97 -0
- instaui/arco/components/menu.py +88 -0
- instaui/arco/components/modal.py +97 -0
- instaui/arco/components/overflow_list.py +29 -0
- instaui/arco/components/page_header.py +29 -0
- instaui/arco/components/pagination.py +45 -0
- instaui/arco/components/pop_confirm.py +58 -0
- instaui/arco/components/popover.py +32 -0
- instaui/arco/components/progress.py +14 -0
- instaui/arco/components/radio.py +40 -0
- instaui/arco/components/radio_group.py +42 -0
- instaui/arco/components/rate.py +45 -0
- instaui/arco/components/resize_box.py +62 -0
- instaui/arco/components/result.py +14 -0
- instaui/arco/components/select.py +182 -0
- instaui/arco/components/skeleton.py +14 -0
- instaui/arco/components/slider.py +38 -0
- instaui/arco/components/space.py +14 -0
- instaui/arco/components/spin.py +14 -0
- instaui/arco/components/split.py +76 -0
- instaui/arco/components/statistic.py +14 -0
- instaui/arco/components/steps.py +32 -0
- instaui/arco/components/switch.py +57 -0
- instaui/arco/components/tab_pane.py +12 -0
- instaui/arco/components/table.py +276 -0
- instaui/arco/components/tabs.py +101 -0
- instaui/arco/components/tag.py +42 -0
- instaui/arco/components/textarea.py +84 -0
- instaui/arco/components/time_picker.py +76 -0
- instaui/arco/components/timeline.py +14 -0
- instaui/arco/components/tooltip.py +29 -0
- instaui/arco/components/transfer.py +58 -0
- instaui/arco/components/tree.py +120 -0
- instaui/arco/components/tree_select.py +86 -0
- instaui/arco/components/trigger.py +58 -0
- instaui/arco/components/typography.py +142 -0
- instaui/arco/components/upload.py +71 -0
- instaui/arco/components/verification_code.py +58 -0
- instaui/arco/components/watermark.py +14 -0
- instaui/arco/locales/__init__.py +4 -0
- instaui/arco/locales/_index.py +31 -0
- instaui/arco/locales/en_us.py +227 -0
- instaui/arco/locales/zh_cn.py +224 -0
- instaui/arco/setup.py +36 -0
- instaui/arco/static/instaui-arco.css +1 -0
- instaui/arco/static/instaui-arco.js +55771 -0
- instaui/arco/types.py +24 -0
- instaui/boot_info.py +43 -0
- instaui/common/jsonable.py +37 -0
- instaui/components/__init__.py +0 -0
- instaui/components/column.py +26 -0
- instaui/components/component.py +47 -0
- instaui/components/content.py +34 -0
- instaui/components/directive.py +55 -0
- instaui/components/element.py +573 -0
- instaui/components/grid.py +213 -0
- instaui/components/html/__init__.py +49 -0
- instaui/components/html/_mixins.py +34 -0
- instaui/components/html/_preset.py +4 -0
- instaui/components/html/button.py +38 -0
- instaui/components/html/checkbox.py +35 -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/heading.py +51 -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 +29 -0
- instaui/components/html/range.py +48 -0
- instaui/components/html/select.py +69 -0
- instaui/components/html/span.py +19 -0
- instaui/components/html/table.py +36 -0
- instaui/components/html/textarea.py +28 -0
- instaui/components/html/ul.py +20 -0
- instaui/components/label.py +5 -0
- instaui/components/markdown/markdown.js +33 -0
- instaui/components/markdown/markdown.py +41 -0
- instaui/components/markdown/static/github-markdown.css +12 -0
- instaui/components/markdown/static/marked.esm.js +2579 -0
- instaui/components/match.py +108 -0
- instaui/components/row.py +17 -0
- instaui/components/shiki_code/shiki_code.js +126 -0
- instaui/components/shiki_code/shiki_code.py +99 -0
- instaui/components/shiki_code/static/langs/css.mjs +5 -0
- instaui/components/shiki_code/static/langs/markdown.mjs +5 -0
- instaui/components/shiki_code/static/langs/python.mjs +5 -0
- instaui/components/shiki_code/static/langs/shell.mjs +2 -0
- instaui/components/shiki_code/static/langs/shellscript.mjs +5 -0
- instaui/components/shiki_code/static/shiki-core.js +5784 -0
- instaui/components/shiki_code/static/shiki-style.css +179 -0
- instaui/components/shiki_code/static/shiki-transformers.js +461 -0
- instaui/components/shiki_code/static/themes/vitesse-dark.mjs +2 -0
- instaui/components/shiki_code/static/themes/vitesse-light.mjs +2 -0
- instaui/components/slot.py +81 -0
- instaui/components/transition_group.py +9 -0
- instaui/components/value_element.py +52 -0
- instaui/components/vfor.py +142 -0
- instaui/components/vif.py +42 -0
- instaui/consts.py +23 -0
- instaui/dependencies/component_dependency.py +22 -0
- instaui/dependencies/plugin_dependency.py +28 -0
- instaui/event/event_mixin.py +12 -0
- instaui/event/js_event.py +82 -0
- instaui/event/vue_event.py +66 -0
- instaui/event/web_event.py +123 -0
- instaui/experimental/__init__.py +3 -0
- instaui/experimental/debug.py +48 -0
- instaui/extra_libs/_echarts.py +3 -0
- instaui/extra_libs/_import_error.py +9 -0
- instaui/extra_libs/_mermaid.py +3 -0
- instaui/extra_libs/_shiki_code.py +3 -0
- instaui/fastapi_server/_utils.py +42 -0
- instaui/fastapi_server/_uvicorn.py +37 -0
- instaui/fastapi_server/debug_mode_router.py +60 -0
- instaui/fastapi_server/dependency_router.py +28 -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/resource.py +30 -0
- instaui/fastapi_server/server.py +308 -0
- instaui/fastapi_server/watch_router.py +53 -0
- instaui/handlers/_utils.py +88 -0
- instaui/handlers/event_handler.py +60 -0
- instaui/handlers/watch_handler.py +61 -0
- instaui/html_tools.py +94 -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 +13 -0
- instaui/runtime/__init__.py +29 -0
- instaui/runtime/_app.py +234 -0
- instaui/runtime/_inner_helper.py +9 -0
- instaui/runtime/_link_manager.py +89 -0
- instaui/runtime/context.py +47 -0
- instaui/runtime/dataclass.py +30 -0
- instaui/runtime/resource.py +65 -0
- instaui/runtime/scope.py +133 -0
- instaui/runtime/ui_state_scope.py +15 -0
- instaui/settings/__init__.py +4 -0
- instaui/settings/__settings.py +13 -0
- instaui/shadcn_classless/_index.py +42 -0
- instaui/shadcn_classless/static/shadcn-classless.css +403 -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 +273 -0
- instaui/spa_router/_functions.py +122 -0
- instaui/spa_router/_install.py +11 -0
- instaui/spa_router/_route_model.py +117 -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 +60 -0
- instaui/static/insta-ui.css +1 -0
- instaui/static/insta-ui.esm-browser.prod.js +3717 -0
- instaui/static/insta-ui.ico +0 -0
- instaui/static/insta-ui.js.map +1 -0
- instaui/static/instaui-tools-browser.js +511 -0
- instaui/static/templates/debug/sse.html +117 -0
- instaui/static/templates/web.html +74 -0
- instaui/static/templates/webview.html +78 -0
- instaui/static/templates/zero.html +71 -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 +6 -0
- instaui/systems/func_system.py +119 -0
- instaui/systems/js_system.py +22 -0
- instaui/systems/module_system.py +46 -0
- instaui/systems/pydantic_system.py +27 -0
- instaui/systems/string_system.py +10 -0
- instaui/tailwind/__init__.py +6 -0
- instaui/tailwind/_index.py +24 -0
- instaui/tailwind/static/tailwindcss-v3.min.js +62 -0
- instaui/tailwind/static/tailwindcss-v4.min.js +8 -0
- instaui/template/__init__.py +4 -0
- instaui/template/_utils.py +23 -0
- instaui/template/env.py +7 -0
- instaui/template/web_template.py +49 -0
- instaui/template/webview_template.py +48 -0
- instaui/template/zero_template.py +105 -0
- instaui/ui/__init__.py +144 -0
- instaui/ui/__init__.pyi +149 -0
- instaui/ui/events.py +25 -0
- instaui/ui_functions/input_slient_data.py +16 -0
- instaui/ui_functions/server.py +15 -0
- instaui/ui_functions/str_format.py +36 -0
- instaui/ui_functions/ui_page.py +16 -0
- instaui/ui_functions/ui_types.py +13 -0
- instaui/ui_functions/url_location.py +33 -0
- instaui/vars/_types.py +8 -0
- instaui/vars/data.py +68 -0
- instaui/vars/element_ref.py +40 -0
- instaui/vars/event_context.py +49 -0
- instaui/vars/event_extend.py +0 -0
- instaui/vars/js_computed.py +117 -0
- instaui/vars/mixin_types/common_type.py +5 -0
- instaui/vars/mixin_types/element_binding.py +16 -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 +90 -0
- instaui/vars/ref.py +103 -0
- instaui/vars/slot_prop.py +46 -0
- instaui/vars/state.py +97 -0
- instaui/vars/types.py +24 -0
- instaui/vars/vfor_item.py +204 -0
- instaui/vars/vue_computed.py +81 -0
- instaui/vars/web_computed.py +209 -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 +110 -0
- instaui/watch/vue_watch.py +77 -0
- instaui/watch/web_watch.py +181 -0
- instaui/webview/__init__.py +2 -0
- instaui/webview/_utils.py +8 -0
- instaui/webview/api.py +72 -0
- instaui/webview/func.py +114 -0
- instaui/webview/index.py +161 -0
- instaui/webview/resource.py +172 -0
- instaui/zero/__init__.py +3 -0
- instaui/zero/func.py +123 -0
- instaui/zero/scope.py +109 -0
- instaui-0.1.15.dist-info/METADATA +152 -0
- instaui-0.1.15.dist-info/RECORD +283 -0
- instaui-0.1.15.dist-info/WHEEL +5 -0
- instaui-0.1.15.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,123 @@
|
|
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.vars.mixin_types.element_binding import ElementBindingMixin
|
8
|
+
from instaui.handlers import event_handler
|
9
|
+
from .event_mixin import EventMixin
|
10
|
+
|
11
|
+
_SYNC_TYPE = "sync"
|
12
|
+
_ASYNC_TYPE = "async"
|
13
|
+
|
14
|
+
P = ParamSpec("P")
|
15
|
+
R = typing.TypeVar("R")
|
16
|
+
_T_input = typing.TypeVar("_T_input")
|
17
|
+
_T_output = typing.TypeVar("_T_output")
|
18
|
+
|
19
|
+
|
20
|
+
class WebEvent(Jsonable, EventMixin, typing.Generic[P, R]):
|
21
|
+
def __init__(
|
22
|
+
self,
|
23
|
+
fn: typing.Callable[P, R],
|
24
|
+
inputs: typing.Sequence[CanInputMixin],
|
25
|
+
outputs: typing.Sequence[CanOutputMixin],
|
26
|
+
):
|
27
|
+
self._inputs = inputs
|
28
|
+
self._outputs = outputs
|
29
|
+
self._fn = fn
|
30
|
+
|
31
|
+
scope = get_current_scope()
|
32
|
+
self._sid = scope.id
|
33
|
+
|
34
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
35
|
+
return self._fn(*args, **kwargs)
|
36
|
+
|
37
|
+
def copy_with_extends(self, extends: typing.Sequence[CanInputMixin]):
|
38
|
+
return WebEvent(
|
39
|
+
fn=self._fn,
|
40
|
+
inputs=list(self._inputs) + list(extends),
|
41
|
+
outputs=self._outputs,
|
42
|
+
)
|
43
|
+
|
44
|
+
def event_type(self):
|
45
|
+
return "web"
|
46
|
+
|
47
|
+
def _to_json_dict(self):
|
48
|
+
app = get_app_slot()
|
49
|
+
|
50
|
+
for _input in self._inputs:
|
51
|
+
if isinstance(_input, ElementBindingMixin):
|
52
|
+
_input._mark_used()
|
53
|
+
|
54
|
+
hkey = event_handler.create_handler_key(
|
55
|
+
page_path=app.page_path, handler=self._fn
|
56
|
+
)
|
57
|
+
|
58
|
+
event_handler.register_event_handler(
|
59
|
+
hkey, self._fn, self._outputs, self._inputs
|
60
|
+
)
|
61
|
+
|
62
|
+
data = {}
|
63
|
+
data["type"] = self.event_type()
|
64
|
+
data["fType"] = (
|
65
|
+
_ASYNC_TYPE if inspect.iscoroutinefunction(self._fn) else _SYNC_TYPE
|
66
|
+
)
|
67
|
+
data["hKey"] = hkey
|
68
|
+
data["sid"] = self._sid
|
69
|
+
|
70
|
+
if self._inputs:
|
71
|
+
data["bind"] = [
|
72
|
+
binding._to_input_config()
|
73
|
+
if isinstance(binding, CanInputMixin)
|
74
|
+
else binding
|
75
|
+
for binding in self._inputs
|
76
|
+
]
|
77
|
+
|
78
|
+
if self._outputs:
|
79
|
+
data["set"] = [ref._to_output_config() for ref in self._outputs]
|
80
|
+
|
81
|
+
return data
|
82
|
+
|
83
|
+
|
84
|
+
def event(
|
85
|
+
*,
|
86
|
+
inputs: typing.Optional[typing.Sequence] = None,
|
87
|
+
outputs: typing.Optional[typing.Sequence] = None,
|
88
|
+
):
|
89
|
+
"""
|
90
|
+
Creates an event handler decorator for binding reactive logic to component events.
|
91
|
+
|
92
|
+
Args:
|
93
|
+
inputs (typing.Optional[typing.Sequence], optional): Reactive sources (state objects, computed properties)
|
94
|
+
that should be accessible during event handling.
|
95
|
+
These values will be passed to the decorated function
|
96
|
+
when the event fires.
|
97
|
+
outputs (typing.Optional[typing.Sequence], optional): Targets (state variables, UI elements) that should
|
98
|
+
update when this handler executes. Used for coordinating
|
99
|
+
interface updates after the event is processed.
|
100
|
+
|
101
|
+
# Example:
|
102
|
+
.. code-block:: python
|
103
|
+
from instaui import ui, html
|
104
|
+
|
105
|
+
a = ui.state(0)
|
106
|
+
|
107
|
+
@ui.event(inputs=[a], outputs=[a])
|
108
|
+
def plus_one(a):
|
109
|
+
return a + 1
|
110
|
+
|
111
|
+
html.button("click me").on_click(plus_one)
|
112
|
+
html.paragraph(a)
|
113
|
+
|
114
|
+
"""
|
115
|
+
|
116
|
+
def wrapper(func: typing.Callable[P, R]):
|
117
|
+
return WebEvent(
|
118
|
+
func,
|
119
|
+
inputs or [],
|
120
|
+
outputs=outputs or [],
|
121
|
+
)
|
122
|
+
|
123
|
+
return wrapper
|
@@ -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
|
+
import asyncio
|
2
|
+
import logging
|
3
|
+
import typing
|
4
|
+
from fastapi import FastAPI, Request, Depends
|
5
|
+
from fastapi.responses import StreamingResponse
|
6
|
+
from instaui.runtime.context import get_context
|
7
|
+
import uuid
|
8
|
+
|
9
|
+
DEBUG_SSE_URL = "/instaui/debug-sse"
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
12
|
+
_task_events: typing.Dict[str, asyncio.Event] = {}
|
13
|
+
|
14
|
+
|
15
|
+
def create_router(app: FastAPI):
|
16
|
+
if get_context().debug_mode:
|
17
|
+
_create_sse(app)
|
18
|
+
|
19
|
+
|
20
|
+
async def event_generator(
|
21
|
+
request: Request, connection_id: str, interval_heart_beat_sec: float = 0.8
|
22
|
+
):
|
23
|
+
logger.debug("debug sse started")
|
24
|
+
task_event = asyncio.Event()
|
25
|
+
_task_events[connection_id] = task_event
|
26
|
+
|
27
|
+
try:
|
28
|
+
while not task_event.is_set():
|
29
|
+
if await request.is_disconnected():
|
30
|
+
break
|
31
|
+
|
32
|
+
yield "data:1\n\n"
|
33
|
+
await asyncio.sleep(interval_heart_beat_sec)
|
34
|
+
|
35
|
+
except asyncio.CancelledError:
|
36
|
+
pass
|
37
|
+
finally:
|
38
|
+
if connection_id in _task_events:
|
39
|
+
del _task_events[connection_id]
|
40
|
+
|
41
|
+
|
42
|
+
def _get_connection_id(request: Request):
|
43
|
+
return str(uuid.uuid4())
|
44
|
+
|
45
|
+
|
46
|
+
def _create_sse(app: FastAPI):
|
47
|
+
@app.get(DEBUG_SSE_URL)
|
48
|
+
async def events(
|
49
|
+
request: Request, connection_id: str = Depends(_get_connection_id)
|
50
|
+
):
|
51
|
+
return StreamingResponse(
|
52
|
+
event_generator(request, connection_id), media_type="text/event-stream"
|
53
|
+
)
|
54
|
+
|
55
|
+
|
56
|
+
def when_server_reload():
|
57
|
+
for task_id, task in _task_events.items():
|
58
|
+
task.set()
|
59
|
+
|
60
|
+
_task_events.clear()
|
@@ -0,0 +1,28 @@
|
|
1
|
+
from fastapi import FastAPI
|
2
|
+
from fastapi.responses import FileResponse
|
3
|
+
|
4
|
+
from instaui.fastapi_server import resource
|
5
|
+
|
6
|
+
|
7
|
+
URL = f"{resource.URL}/{{hash_part:path}}/{{file_name:path}}"
|
8
|
+
|
9
|
+
|
10
|
+
def create_router(app: FastAPI):
|
11
|
+
_dependency_handler(app)
|
12
|
+
|
13
|
+
|
14
|
+
def _dependency_handler(app: FastAPI):
|
15
|
+
@app.get(URL)
|
16
|
+
def _(hash_part: str, file_name: str) -> FileResponse:
|
17
|
+
hash_part_with_extend_paths = hash_part.split("/", maxsplit=1)
|
18
|
+
hash_part = hash_part_with_extend_paths[0]
|
19
|
+
extend_path = None if len(hash_part_with_extend_paths) == 1 else hash_part_with_extend_paths[1]
|
20
|
+
|
21
|
+
folder = resource.get_folder_path(hash_part)
|
22
|
+
if extend_path:
|
23
|
+
folder = folder.joinpath(extend_path)
|
24
|
+
local_file = folder / file_name
|
25
|
+
|
26
|
+
return FileResponse(
|
27
|
+
local_file, headers={"Cache-Control": "public, max-age=3600"}
|
28
|
+
)
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import inspect
|
2
|
+
from typing import Dict
|
3
|
+
from fastapi import FastAPI
|
4
|
+
|
5
|
+
from instaui.handlers import event_handler
|
6
|
+
from instaui.runtime.context import get_context
|
7
|
+
|
8
|
+
from . import _utils
|
9
|
+
|
10
|
+
|
11
|
+
def create_router(app: FastAPI):
|
12
|
+
_async_handler(app)
|
13
|
+
_sync_handler(app)
|
14
|
+
|
15
|
+
|
16
|
+
def _async_handler(app: FastAPI):
|
17
|
+
@app.post(event_handler.ASYNC_URL)
|
18
|
+
async def _(data: Dict):
|
19
|
+
handler = _get_handler(data)
|
20
|
+
if handler is None:
|
21
|
+
return {"error": "event handler not found"}
|
22
|
+
|
23
|
+
assert inspect.iscoroutinefunction(
|
24
|
+
handler.fn
|
25
|
+
), "handler must be a coroutine function"
|
26
|
+
|
27
|
+
_utils.update_app_page_info(data)
|
28
|
+
|
29
|
+
result = await handler.fn(*handler.get_handler_args(_get_binds_from_data(data)))
|
30
|
+
return _utils.response_data(handler.outputs_binding_count, result)
|
31
|
+
|
32
|
+
|
33
|
+
def _sync_handler(app: FastAPI):
|
34
|
+
@app.post(event_handler.SYNC_URL)
|
35
|
+
def _(data: Dict):
|
36
|
+
handler = _get_handler(data)
|
37
|
+
if handler is None:
|
38
|
+
return {"error": "event handler not found"}
|
39
|
+
|
40
|
+
_utils.update_app_page_info(data)
|
41
|
+
|
42
|
+
result = handler.fn(*handler.get_handler_args(_get_binds_from_data(data)))
|
43
|
+
|
44
|
+
return _utils.response_data(handler.outputs_binding_count, result)
|
45
|
+
|
46
|
+
if get_context().debug_mode:
|
47
|
+
|
48
|
+
@app.get("/instaui/event-infos", tags=["instaui-debug"])
|
49
|
+
def event_infos():
|
50
|
+
return event_handler.get_statistics_info()
|
51
|
+
|
52
|
+
|
53
|
+
def _get_handler(data: Dict):
|
54
|
+
return event_handler.get_handler(data["hKey"])
|
55
|
+
|
56
|
+
|
57
|
+
def _get_binds_from_data(data: Dict):
|
58
|
+
return [bind for bind in data.get("bind", [])]
|
@@ -0,0 +1,19 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Any, Callable
|
3
|
+
from fastapi import Request
|
4
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
5
|
+
from instaui.runtime import new_app_slot, reset_app_slot
|
6
|
+
from .request_context import set_current_request
|
7
|
+
|
8
|
+
|
9
|
+
class RequestContextMiddleware(BaseHTTPMiddleware):
|
10
|
+
async def dispatch(self, request: Request, call_next: Callable) -> Any:
|
11
|
+
set_current_request(request)
|
12
|
+
system_slot_token = new_app_slot("web")
|
13
|
+
|
14
|
+
try:
|
15
|
+
response = await call_next(request)
|
16
|
+
finally:
|
17
|
+
reset_app_slot(system_slot_token)
|
18
|
+
|
19
|
+
return response
|
@@ -0,0 +1,19 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Optional
|
3
|
+
from fastapi import Request
|
4
|
+
from contextvars import ContextVar, Token
|
5
|
+
|
6
|
+
current_request_ctx :ContextVar[Optional[Request]]= ContextVar("current_request", default=None)
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
def set_current_request(request: Request) :
|
11
|
+
return current_request_ctx.set(request)
|
12
|
+
|
13
|
+
def reset_current_request(token: Token) -> None:
|
14
|
+
current_request_ctx.reset(token)
|
15
|
+
|
16
|
+
|
17
|
+
def get_current_request() -> Request:
|
18
|
+
return current_request_ctx.get() # type: ignore
|
19
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from typing import Dict
|
3
|
+
from instaui.systems import file_system
|
4
|
+
from instaui.version import __version__ as _INSTA_VERSION
|
5
|
+
|
6
|
+
URL = f"/_instaui_{_INSTA_VERSION}/resource"
|
7
|
+
_THashPart = str
|
8
|
+
_HASH_PART_MAP: Dict[_THashPart, Path] = {}
|
9
|
+
_PATH_URL_MAP: Dict[Path, _THashPart] = {}
|
10
|
+
|
11
|
+
|
12
|
+
def get_folder_path(hash_part: str) -> Path:
|
13
|
+
return _HASH_PART_MAP[hash_part]
|
14
|
+
|
15
|
+
|
16
|
+
def record_resource(path: Path):
|
17
|
+
path = Path(path).resolve()
|
18
|
+
is_file = path.is_file()
|
19
|
+
|
20
|
+
folder_path = path.parent if is_file else path
|
21
|
+
|
22
|
+
if folder_path not in _HASH_PART_MAP:
|
23
|
+
hash_part = file_system.generate_hash_name_from_path(folder_path)
|
24
|
+
_HASH_PART_MAP[hash_part] = folder_path
|
25
|
+
else:
|
26
|
+
hash_part = _PATH_URL_MAP[folder_path]
|
27
|
+
|
28
|
+
folder_url = f"{URL}/{hash_part}/"
|
29
|
+
|
30
|
+
return f"{folder_url}{path.name}" if is_file else folder_url
|