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,61 @@
|
|
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: int = 2
|
22
|
+
):
|
23
|
+
logger.debug("debug sse started")
|
24
|
+
task_event = asyncio.Event()
|
25
|
+
_task_events[connection_id] = task_event
|
26
|
+
|
27
|
+
# yield "event: task\ndata: 0 \n\n\n"
|
28
|
+
try:
|
29
|
+
while not task_event.is_set():
|
30
|
+
if await request.is_disconnected():
|
31
|
+
break
|
32
|
+
|
33
|
+
yield "data:1\n\n"
|
34
|
+
await asyncio.sleep(interval_heart_beat_sec)
|
35
|
+
|
36
|
+
except asyncio.CancelledError:
|
37
|
+
pass
|
38
|
+
finally:
|
39
|
+
if connection_id in _task_events:
|
40
|
+
del _task_events[connection_id]
|
41
|
+
|
42
|
+
|
43
|
+
def _get_connection_id(request: Request):
|
44
|
+
return str(uuid.uuid4())
|
45
|
+
|
46
|
+
|
47
|
+
def _create_sse(app: FastAPI):
|
48
|
+
@app.get(DEBUG_SSE_URL)
|
49
|
+
async def events(
|
50
|
+
request: Request, connection_id: str = Depends(_get_connection_id)
|
51
|
+
):
|
52
|
+
return StreamingResponse(
|
53
|
+
event_generator(request, connection_id), media_type="text/event-stream"
|
54
|
+
)
|
55
|
+
|
56
|
+
|
57
|
+
def when_server_reload():
|
58
|
+
for task_id, task in _task_events.items():
|
59
|
+
task.set()
|
60
|
+
|
61
|
+
_task_events.clear()
|
@@ -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,246 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import inspect
|
3
|
+
import os
|
4
|
+
import multiprocessing
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import Any, Optional, Set
|
7
|
+
import __main__
|
8
|
+
|
9
|
+
from fastapi import FastAPI
|
10
|
+
from fastapi import Request
|
11
|
+
from fastapi.responses import FileResponse, HTMLResponse
|
12
|
+
from fastapi.staticfiles import StaticFiles
|
13
|
+
import uvicorn
|
14
|
+
from uvicorn.supervisors import ChangeReload
|
15
|
+
|
16
|
+
from instaui.html_tools import to_json
|
17
|
+
from instaui.launch_collector import get_launch_collector
|
18
|
+
from instaui.page_info import PageInfo
|
19
|
+
from instaui.handlers import config_handler
|
20
|
+
from instaui.systems import file_system
|
21
|
+
from instaui import consts
|
22
|
+
from instaui.runtime._app import get_app_slot
|
23
|
+
from instaui.template import web_template
|
24
|
+
from . import config_router
|
25
|
+
from . import event_router
|
26
|
+
from . import watch_router
|
27
|
+
from . import debug_mode_router
|
28
|
+
from .middlewares import RequestContextMiddleware
|
29
|
+
from ._uvicorn import UvicornServer
|
30
|
+
|
31
|
+
|
32
|
+
APP_IMPORT_STRING = "instaui.fastapi_server.server:Server._instance.app"
|
33
|
+
|
34
|
+
|
35
|
+
class Server:
|
36
|
+
_instance: Optional[Server] = None
|
37
|
+
|
38
|
+
@classmethod
|
39
|
+
def get_instance(cls):
|
40
|
+
if cls._instance is None:
|
41
|
+
cls._instance = cls()
|
42
|
+
return cls._instance
|
43
|
+
|
44
|
+
def __init__(self):
|
45
|
+
self.app = FastAPI()
|
46
|
+
self.app.add_middleware(RequestContextMiddleware)
|
47
|
+
config_router.create_router(self.app)
|
48
|
+
event_router.create_router(self.app)
|
49
|
+
watch_router.create_router(self.app)
|
50
|
+
debug_mode_router.create_router(self.app)
|
51
|
+
|
52
|
+
self._static_dir_url = _add_static_dir(self.app)
|
53
|
+
|
54
|
+
for page_info in get_launch_collector()._page_router.values():
|
55
|
+
self.register_page(page_info)
|
56
|
+
|
57
|
+
self._registered_static_routes: Set[str] = set()
|
58
|
+
|
59
|
+
def register_page(self, info: PageInfo):
|
60
|
+
is_async = inspect.iscoroutinefunction(info.func)
|
61
|
+
|
62
|
+
self._remove_route(info.path)
|
63
|
+
|
64
|
+
key = info.create_key()
|
65
|
+
config_handler.register_handler(key, info.func)
|
66
|
+
config_fetch_url = (
|
67
|
+
config_handler.ASYNC_URL if is_async else config_handler.SYNC_URL
|
68
|
+
)
|
69
|
+
|
70
|
+
@self.app.get(info.path)
|
71
|
+
def wrapper(request: Request):
|
72
|
+
page_loadding_json = None
|
73
|
+
if info.page_loading:
|
74
|
+
loadding_result = info.page_loading()
|
75
|
+
if loadding_result:
|
76
|
+
return loadding_result
|
77
|
+
page_loadding_json = to_json()
|
78
|
+
|
79
|
+
html = self._to_web_html(
|
80
|
+
page_info=info,
|
81
|
+
config_fetch_url=config_fetch_url,
|
82
|
+
config_fetch_key=key,
|
83
|
+
query_path=info.path,
|
84
|
+
request=request,
|
85
|
+
page_loadding_json=page_loadding_json,
|
86
|
+
)
|
87
|
+
response = HTMLResponse(html)
|
88
|
+
return response
|
89
|
+
|
90
|
+
return wrapper
|
91
|
+
|
92
|
+
def _to_web_html(
|
93
|
+
self,
|
94
|
+
*,
|
95
|
+
page_info: PageInfo,
|
96
|
+
query_path: str,
|
97
|
+
config_json: Optional[str] = None,
|
98
|
+
config_fetch_url: Optional[str] = None,
|
99
|
+
config_fetch_key: Optional[str] = None,
|
100
|
+
page_loadding_json: Optional[str] = None,
|
101
|
+
request: Request,
|
102
|
+
):
|
103
|
+
system_slot = get_app_slot()
|
104
|
+
html_resource = system_slot._html_resource
|
105
|
+
page_info.apply_settings(html_resource)
|
106
|
+
|
107
|
+
html_resource.add_import_map(
|
108
|
+
"vue", self._static_dir_url + f"/{consts.VUE_ES_JS_PATH.name}"
|
109
|
+
)
|
110
|
+
|
111
|
+
html_resource.add_import_map(
|
112
|
+
"instaui", self._static_dir_url + f"/{consts.APP_ES_JS_PATH.name}"
|
113
|
+
)
|
114
|
+
html_resource.add_css_link(consts.APP_CSS_PATH)
|
115
|
+
|
116
|
+
if html_resource.use_tailwind:
|
117
|
+
tailwind_url = self._static_dir_url + f"/{consts.TAILWIND_JS_PATH.name}"
|
118
|
+
html_resource.add_js_link(tailwind_url)
|
119
|
+
|
120
|
+
# register custom components
|
121
|
+
# TODO: use one get api to get all components
|
122
|
+
for component in system_slot._js_components:
|
123
|
+
if not component.esm:
|
124
|
+
continue
|
125
|
+
url = self.add_static_file_route(component.esm)
|
126
|
+
html_resource.add_vue_app_component(name=component.name, url=url)
|
127
|
+
|
128
|
+
if component.css:
|
129
|
+
for css_link in component.css:
|
130
|
+
html_resource.add_css_link(css_link)
|
131
|
+
|
132
|
+
# register custom plugins
|
133
|
+
for plugin in system_slot._plugins:
|
134
|
+
if not plugin.esm:
|
135
|
+
continue
|
136
|
+
url = self.add_static_file_route(plugin.esm)
|
137
|
+
html_resource.add_vue_app_use(url)
|
138
|
+
|
139
|
+
if plugin.css:
|
140
|
+
for css_link in plugin.css:
|
141
|
+
html_resource.add_css_link(css_link)
|
142
|
+
|
143
|
+
# css file link to web static link
|
144
|
+
html_resource._css_links = {
|
145
|
+
self.add_static_file_route(link) if isinstance(link, Path) else link: None
|
146
|
+
for link, attrs in html_resource._css_links.items()
|
147
|
+
}
|
148
|
+
|
149
|
+
# js file link to web static link
|
150
|
+
for info in html_resource._js_links:
|
151
|
+
if isinstance(info.link, Path):
|
152
|
+
info.link = self.add_static_file_route(info.link)
|
153
|
+
|
154
|
+
prefix = request.headers.get(
|
155
|
+
"X-Forwarded-Prefix", request.scope.get("root_path", "")
|
156
|
+
)
|
157
|
+
|
158
|
+
result = web_template.render_web_html(
|
159
|
+
config_json=config_json,
|
160
|
+
config_fetch_url=config_fetch_url,
|
161
|
+
config_fetch_key=config_fetch_key,
|
162
|
+
query_path_params=str(request.path_params),
|
163
|
+
query_params=dict(request.query_params),
|
164
|
+
query_path=query_path,
|
165
|
+
page_loadding_json=page_loadding_json,
|
166
|
+
prefix=prefix,
|
167
|
+
)
|
168
|
+
get_app_slot().reset_html_resource()
|
169
|
+
return result
|
170
|
+
|
171
|
+
def add_static_file_route(self, local_file: Path) -> str:
|
172
|
+
path = file_system.generate_static_url_from_file_path(local_file)
|
173
|
+
if path in self._registered_static_routes:
|
174
|
+
return path
|
175
|
+
|
176
|
+
@self.app.get(path)
|
177
|
+
def _() -> FileResponse:
|
178
|
+
return FileResponse(
|
179
|
+
local_file, headers={"Cache-Control": "public, max-age=3600"}
|
180
|
+
)
|
181
|
+
|
182
|
+
return path
|
183
|
+
|
184
|
+
def _remove_route(self, path: str) -> None:
|
185
|
+
self.app.routes[:] = [
|
186
|
+
r for r in self.app.routes if getattr(r, "path", None) != path
|
187
|
+
]
|
188
|
+
|
189
|
+
def try_close_server(self):
|
190
|
+
UvicornServer.instance.should_exit = True
|
191
|
+
|
192
|
+
def run(
|
193
|
+
self,
|
194
|
+
host="0.0.0.0",
|
195
|
+
port=8080,
|
196
|
+
reload: bool = True,
|
197
|
+
reload_dirs: str = ".",
|
198
|
+
reload_includes: str = "*.py",
|
199
|
+
reload_excludes: str = ".*, .py[cod], .sw.*, ~*",
|
200
|
+
log_level="info",
|
201
|
+
workers: int | None = None,
|
202
|
+
uds: str | None = None,
|
203
|
+
**kwargs: Any,
|
204
|
+
):
|
205
|
+
if multiprocessing.current_process().name != "MainProcess":
|
206
|
+
return
|
207
|
+
|
208
|
+
if reload and not hasattr(__main__, "__file__"):
|
209
|
+
reload = False
|
210
|
+
|
211
|
+
config = uvicorn.Config(
|
212
|
+
APP_IMPORT_STRING if reload else self.app,
|
213
|
+
host=host,
|
214
|
+
port=port,
|
215
|
+
reload=reload,
|
216
|
+
log_level=log_level,
|
217
|
+
workers=workers,
|
218
|
+
uds=uds,
|
219
|
+
reload_includes=_split_args(reload_includes) if reload else None,
|
220
|
+
reload_excludes=_split_args(reload_excludes) if reload else None,
|
221
|
+
reload_dirs=_split_args(reload_dirs) if reload else None,
|
222
|
+
**kwargs,
|
223
|
+
)
|
224
|
+
|
225
|
+
UvicornServer.create_singleton(config, [debug_mode_router.when_server_reload])
|
226
|
+
|
227
|
+
if config.should_reload:
|
228
|
+
ChangeReload(config, target=UvicornServer.instance.run, sockets=[]).run()
|
229
|
+
else:
|
230
|
+
UvicornServer.instance.run()
|
231
|
+
|
232
|
+
if config.uds:
|
233
|
+
os.remove(config.uds) # pragma: py-win32
|
234
|
+
|
235
|
+
def run_with(self, app):
|
236
|
+
assert isinstance(app, FastAPI), "app must be a FastAPI instance"
|
237
|
+
|
238
|
+
|
239
|
+
def _add_static_dir(app: FastAPI):
|
240
|
+
url = file_system.generate_static_url_from_file_path(consts._STATIC_DIR)
|
241
|
+
app.mount(url, StaticFiles(directory=consts._STATIC_DIR))
|
242
|
+
return url
|
243
|
+
|
244
|
+
|
245
|
+
def _split_args(args: str):
|
246
|
+
return [a.strip() for a in args.split(",")]
|
@@ -0,0 +1,53 @@
|
|
1
|
+
from typing import Dict
|
2
|
+
from fastapi import FastAPI
|
3
|
+
from instaui.handlers import watch_handler
|
4
|
+
from instaui.runtime.context import get_context
|
5
|
+
|
6
|
+
from . import _utils
|
7
|
+
|
8
|
+
|
9
|
+
def create_router(app: FastAPI):
|
10
|
+
_async_handler(app)
|
11
|
+
_sync_handler(app)
|
12
|
+
|
13
|
+
|
14
|
+
def _async_handler(app: FastAPI):
|
15
|
+
@app.post(watch_handler.ASYNC_URL)
|
16
|
+
async def _(data: Dict):
|
17
|
+
hkey = data.pop("key")
|
18
|
+
handler_info = watch_handler.get_handler_info(hkey)
|
19
|
+
if handler_info is None:
|
20
|
+
return {"error": "watch handler not found"}
|
21
|
+
|
22
|
+
_utils.update_app_page_info(data)
|
23
|
+
|
24
|
+
result = await handler_info.fn(
|
25
|
+
*handler_info.get_handler_args(_get_binds_from_data(data))
|
26
|
+
)
|
27
|
+
return _utils.response_data(handler_info.outputs_binding_count, result)
|
28
|
+
|
29
|
+
|
30
|
+
def _sync_handler(app: FastAPI):
|
31
|
+
@app.post(watch_handler.SYNC_URL)
|
32
|
+
def _(data: Dict):
|
33
|
+
hkey = data.pop("key")
|
34
|
+
handler_info = watch_handler.get_handler_info(hkey)
|
35
|
+
if handler_info is None:
|
36
|
+
return {"error": "watch handler not found"}
|
37
|
+
|
38
|
+
_utils.update_app_page_info(data)
|
39
|
+
|
40
|
+
result = handler_info.fn(
|
41
|
+
*handler_info.get_handler_args(_get_binds_from_data(data))
|
42
|
+
)
|
43
|
+
return _utils.response_data(handler_info.outputs_binding_count, result)
|
44
|
+
|
45
|
+
if get_context().debug_mode:
|
46
|
+
|
47
|
+
@app.get("/instaui/watch-infos", tags=["instaui-debug"])
|
48
|
+
def watch_infos():
|
49
|
+
return watch_handler.get_statistics_info()
|
50
|
+
|
51
|
+
|
52
|
+
def _get_binds_from_data(data: Dict):
|
53
|
+
return data.get("input", [])
|
@@ -0,0 +1,66 @@
|
|
1
|
+
from dataclasses import dataclass, field
|
2
|
+
from typing import Callable, List, Mapping, Optional
|
3
|
+
|
4
|
+
import pydantic_core
|
5
|
+
from instaui.systems import func_system, pydantic_system
|
6
|
+
from instaui.runtime.context import get_context
|
7
|
+
|
8
|
+
|
9
|
+
def create_handler_key(
|
10
|
+
page_path: str,
|
11
|
+
handler: Callable,
|
12
|
+
):
|
13
|
+
_, lineno, _ = func_system.get_function_location_info(handler)
|
14
|
+
|
15
|
+
if get_context().debug_mode:
|
16
|
+
return f"page:{page_path}|line:{lineno}"
|
17
|
+
|
18
|
+
return f"{page_path}|{lineno}"
|
19
|
+
|
20
|
+
|
21
|
+
@dataclass
|
22
|
+
class HandlerInfo:
|
23
|
+
fn: Callable
|
24
|
+
fn_location_info: str
|
25
|
+
outputs_binding_count: int = 0
|
26
|
+
hanlder_param_converters: List[pydantic_system.TypeAdapterProtocol] = field(
|
27
|
+
default_factory=list
|
28
|
+
)
|
29
|
+
|
30
|
+
def get_handler_args(self, input_values: List):
|
31
|
+
try:
|
32
|
+
return [
|
33
|
+
param_converter.to_python_value(value)
|
34
|
+
for param_converter, value in zip(
|
35
|
+
self.hanlder_param_converters, input_values
|
36
|
+
)
|
37
|
+
]
|
38
|
+
except pydantic_core._pydantic_core.ValidationError as e:
|
39
|
+
raise ValueError(f"invalid input[{self.fn_location_info}]: {e}") from None
|
40
|
+
|
41
|
+
@classmethod
|
42
|
+
def from_handler(
|
43
|
+
cls,
|
44
|
+
handler: Callable,
|
45
|
+
outputs_binding_count: int,
|
46
|
+
custom_type_adapter_map: Optional[
|
47
|
+
Mapping[int, pydantic_system.TypeAdapterProtocol]
|
48
|
+
] = None,
|
49
|
+
):
|
50
|
+
custom_type_adapter_map = custom_type_adapter_map or {}
|
51
|
+
params_infos = func_system.get_fn_params_infos(handler)
|
52
|
+
param_converters = [
|
53
|
+
custom_type_adapter_map.get(
|
54
|
+
idx, pydantic_system.create_type_adapter(param_type)
|
55
|
+
)
|
56
|
+
for idx, (_, param_type) in enumerate(params_infos)
|
57
|
+
]
|
58
|
+
|
59
|
+
file, lineno, _ = func_system.get_function_location_info(handler)
|
60
|
+
|
61
|
+
return cls(
|
62
|
+
handler,
|
63
|
+
f'File "{file}", line {lineno}',
|
64
|
+
outputs_binding_count,
|
65
|
+
hanlder_param_converters=param_converters,
|
66
|
+
)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import threading
|
2
|
+
from typing import Callable, Dict, Optional
|
3
|
+
|
4
|
+
from instaui.runtime.context import get_context
|
5
|
+
from instaui.systems import func_system
|
6
|
+
|
7
|
+
|
8
|
+
ASYNC_URL = "/instaui/computed/async"
|
9
|
+
SYNC_URL = "/instaui/computed/sync"
|
10
|
+
INIT_URL = "/instaui/computed/init"
|
11
|
+
|
12
|
+
_computed_handlers: Dict[str, Callable] = {}
|
13
|
+
dict_lock = threading.Lock()
|
14
|
+
|
15
|
+
|
16
|
+
def register_handler(key: str, handler: Callable):
|
17
|
+
if key in _computed_handlers:
|
18
|
+
return
|
19
|
+
with dict_lock:
|
20
|
+
_computed_handlers[key] = handler
|
21
|
+
|
22
|
+
|
23
|
+
def get_handler(key: str) -> Optional[Callable]:
|
24
|
+
return _computed_handlers.get(key)
|
25
|
+
|
26
|
+
|
27
|
+
def get_statistics_info():
|
28
|
+
return {
|
29
|
+
"_computed_handlers count": len(_computed_handlers),
|
30
|
+
"_computed_handlers keys": list(_computed_handlers.keys()),
|
31
|
+
}
|
32
|
+
|
33
|
+
|
34
|
+
def create_handler_key(
|
35
|
+
page_path: str,
|
36
|
+
handler: Callable,
|
37
|
+
):
|
38
|
+
_, lineno, _ = func_system.get_function_location_info(handler)
|
39
|
+
|
40
|
+
if get_context().debug_mode:
|
41
|
+
return f"path:{page_path}|line:{lineno}"
|
42
|
+
return f"{page_path}|{lineno}"
|
@@ -0,0 +1,13 @@
|
|
1
|
+
from typing import Callable, Dict
|
2
|
+
|
3
|
+
ASYNC_URL = "/instaui/config/async"
|
4
|
+
SYNC_URL = "/instaui/config/sync"
|
5
|
+
_handlers: Dict[str, Callable] = {}
|
6
|
+
|
7
|
+
|
8
|
+
def register_handler(key: str, handler: Callable):
|
9
|
+
_handlers[key] = handler
|
10
|
+
|
11
|
+
|
12
|
+
def get_handler(key: str) -> Callable:
|
13
|
+
return _handlers[key]
|
@@ -0,0 +1,58 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import threading
|
3
|
+
from typing import Callable, Dict, Optional, Sequence, TYPE_CHECKING
|
4
|
+
from . import _utils
|
5
|
+
import ast
|
6
|
+
from instaui.vars.event_context import DatasetEventContext
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from instaui.vars.mixin_types.py_binding import CanOutputMixin, CanInputMixin
|
10
|
+
|
11
|
+
ASYNC_URL = "/instaui/event/async"
|
12
|
+
SYNC_URL = "/instaui/event/sync"
|
13
|
+
_event_handlers: Dict[str, _utils.HandlerInfo] = {}
|
14
|
+
dict_lock = threading.Lock()
|
15
|
+
|
16
|
+
|
17
|
+
class EventDataSetTypeAdapter:
|
18
|
+
def to_python_value(self, value, *args, **kwargs):
|
19
|
+
return ast.literal_eval(value)
|
20
|
+
|
21
|
+
|
22
|
+
def register_event_handler(
|
23
|
+
key: str,
|
24
|
+
handler: Callable,
|
25
|
+
outputs_binding: Optional[Sequence[CanOutputMixin]],
|
26
|
+
inputs_binding: Optional[Sequence[CanInputMixin]],
|
27
|
+
):
|
28
|
+
if key in _event_handlers:
|
29
|
+
return
|
30
|
+
|
31
|
+
custom_type_adapter_map = {
|
32
|
+
i: EventDataSetTypeAdapter()
|
33
|
+
for i, binding in enumerate(inputs_binding or [])
|
34
|
+
if isinstance(binding, DatasetEventContext)
|
35
|
+
}
|
36
|
+
|
37
|
+
handler_info = _utils.HandlerInfo.from_handler(
|
38
|
+
handler,
|
39
|
+
len(list(outputs_binding)) if outputs_binding else 0,
|
40
|
+
custom_type_adapter_map=custom_type_adapter_map,
|
41
|
+
)
|
42
|
+
|
43
|
+
with dict_lock:
|
44
|
+
_event_handlers[key] = handler_info
|
45
|
+
|
46
|
+
|
47
|
+
def get_handler(key: str) -> Optional[_utils.HandlerInfo]:
|
48
|
+
return _event_handlers.get(key)
|
49
|
+
|
50
|
+
|
51
|
+
def get_statistics_info():
|
52
|
+
return {
|
53
|
+
"_event_handlers count": len(_event_handlers),
|
54
|
+
"_event_handlers keys": list(_event_handlers.keys()),
|
55
|
+
}
|
56
|
+
|
57
|
+
|
58
|
+
create_handler_key = _utils.create_handler_key
|
@@ -0,0 +1,57 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import threading
|
3
|
+
from typing import (
|
4
|
+
Callable,
|
5
|
+
Dict,
|
6
|
+
Generic,
|
7
|
+
TypeVar,
|
8
|
+
)
|
9
|
+
from dataclasses import dataclass
|
10
|
+
|
11
|
+
from instaui.runtime.context import get_context
|
12
|
+
from instaui.systems import func_system
|
13
|
+
from . import _utils
|
14
|
+
|
15
|
+
|
16
|
+
ASYNC_URL = "/instaui/watch/async"
|
17
|
+
SYNC_URL = "/instaui/watch/sync"
|
18
|
+
_watch_handlers: Dict[str, _utils.HandlerInfo] = {}
|
19
|
+
dict_lock = threading.Lock()
|
20
|
+
|
21
|
+
|
22
|
+
def register_handler(key: str, handler: Callable, outputs_binding_count: int):
|
23
|
+
if key in _watch_handlers:
|
24
|
+
return
|
25
|
+
with dict_lock:
|
26
|
+
_watch_handlers[key] = _utils.HandlerInfo.from_handler(
|
27
|
+
handler, outputs_binding_count
|
28
|
+
)
|
29
|
+
|
30
|
+
|
31
|
+
def get_handler_info(key: str) -> _utils.HandlerInfo:
|
32
|
+
return _watch_handlers.get(key) # type: ignore
|
33
|
+
|
34
|
+
|
35
|
+
def get_statistics_info():
|
36
|
+
return {
|
37
|
+
"_watch_handlers count": len(_watch_handlers),
|
38
|
+
"_watch_handlers keys": list(_watch_handlers.keys()),
|
39
|
+
}
|
40
|
+
|
41
|
+
|
42
|
+
def create_handler_key(page_path: str, handler: Callable):
|
43
|
+
_, lineno, _ = func_system.get_function_location_info(handler)
|
44
|
+
|
45
|
+
if get_context().debug_mode:
|
46
|
+
return f"path:{page_path}|line:{lineno}"
|
47
|
+
return f"{page_path}|{lineno}"
|
48
|
+
|
49
|
+
|
50
|
+
_TWatchStateValue = TypeVar("_TWatchStateValue")
|
51
|
+
|
52
|
+
|
53
|
+
@dataclass(frozen=True)
|
54
|
+
class WatchState(Generic[_TWatchStateValue]):
|
55
|
+
new_value: _TWatchStateValue
|
56
|
+
old_value: _TWatchStateValue
|
57
|
+
modified: bool
|