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,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