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.
Files changed (283) hide show
  1. instaui/__init__.py +9 -0
  2. instaui/_helper/observable_helper.py +45 -0
  3. instaui/arco/__init__.py +191 -0
  4. instaui/arco/_settings.py +25 -0
  5. instaui/arco/_use_tools/locale.py +50 -0
  6. instaui/arco/component_types.py +1019 -0
  7. instaui/arco/components/_utils.py +22 -0
  8. instaui/arco/components/affix.py +29 -0
  9. instaui/arco/components/alert.py +42 -0
  10. instaui/arco/components/anchor.py +42 -0
  11. instaui/arco/components/auto_complete.py +96 -0
  12. instaui/arco/components/avatar.py +55 -0
  13. instaui/arco/components/back_top.py +14 -0
  14. instaui/arco/components/badge.py +14 -0
  15. instaui/arco/components/breadcrumb.py +14 -0
  16. instaui/arco/components/button.py +43 -0
  17. instaui/arco/components/calendar.py +47 -0
  18. instaui/arco/components/card.py +14 -0
  19. instaui/arco/components/carousel.py +33 -0
  20. instaui/arco/components/cascader.py +111 -0
  21. instaui/arco/components/checkbox.py +32 -0
  22. instaui/arco/components/collapse.py +31 -0
  23. instaui/arco/components/color_picker.py +45 -0
  24. instaui/arco/components/comment.py +14 -0
  25. instaui/arco/components/config_provider.py +13 -0
  26. instaui/arco/components/date_picker.py +111 -0
  27. instaui/arco/components/descriptions.py +14 -0
  28. instaui/arco/components/divider.py +13 -0
  29. instaui/arco/components/drawer.py +98 -0
  30. instaui/arco/components/dropdown.py +45 -0
  31. instaui/arco/components/empty.py +14 -0
  32. instaui/arco/components/form.py +55 -0
  33. instaui/arco/components/icon.py +17 -0
  34. instaui/arco/components/image.py +33 -0
  35. instaui/arco/components/input.py +102 -0
  36. instaui/arco/components/input_number.py +97 -0
  37. instaui/arco/components/input_password.py +38 -0
  38. instaui/arco/components/input_search.py +37 -0
  39. instaui/arco/components/input_tag.py +110 -0
  40. instaui/arco/components/layout.py +13 -0
  41. instaui/arco/components/layout_content.py +6 -0
  42. instaui/arco/components/layout_footer.py +6 -0
  43. instaui/arco/components/layout_header.py +6 -0
  44. instaui/arco/components/layout_sider.py +53 -0
  45. instaui/arco/components/link.py +36 -0
  46. instaui/arco/components/list.py +68 -0
  47. instaui/arco/components/mention.py +97 -0
  48. instaui/arco/components/menu.py +88 -0
  49. instaui/arco/components/modal.py +97 -0
  50. instaui/arco/components/overflow_list.py +29 -0
  51. instaui/arco/components/page_header.py +29 -0
  52. instaui/arco/components/pagination.py +45 -0
  53. instaui/arco/components/pop_confirm.py +58 -0
  54. instaui/arco/components/popover.py +32 -0
  55. instaui/arco/components/progress.py +14 -0
  56. instaui/arco/components/radio.py +40 -0
  57. instaui/arco/components/radio_group.py +42 -0
  58. instaui/arco/components/rate.py +45 -0
  59. instaui/arco/components/resize_box.py +62 -0
  60. instaui/arco/components/result.py +14 -0
  61. instaui/arco/components/select.py +182 -0
  62. instaui/arco/components/skeleton.py +14 -0
  63. instaui/arco/components/slider.py +38 -0
  64. instaui/arco/components/space.py +14 -0
  65. instaui/arco/components/spin.py +14 -0
  66. instaui/arco/components/split.py +76 -0
  67. instaui/arco/components/statistic.py +14 -0
  68. instaui/arco/components/steps.py +32 -0
  69. instaui/arco/components/switch.py +57 -0
  70. instaui/arco/components/tab_pane.py +12 -0
  71. instaui/arco/components/table.py +276 -0
  72. instaui/arco/components/tabs.py +101 -0
  73. instaui/arco/components/tag.py +42 -0
  74. instaui/arco/components/textarea.py +84 -0
  75. instaui/arco/components/time_picker.py +76 -0
  76. instaui/arco/components/timeline.py +14 -0
  77. instaui/arco/components/tooltip.py +29 -0
  78. instaui/arco/components/transfer.py +58 -0
  79. instaui/arco/components/tree.py +120 -0
  80. instaui/arco/components/tree_select.py +86 -0
  81. instaui/arco/components/trigger.py +58 -0
  82. instaui/arco/components/typography.py +142 -0
  83. instaui/arco/components/upload.py +71 -0
  84. instaui/arco/components/verification_code.py +58 -0
  85. instaui/arco/components/watermark.py +14 -0
  86. instaui/arco/locales/__init__.py +4 -0
  87. instaui/arco/locales/_index.py +31 -0
  88. instaui/arco/locales/en_us.py +227 -0
  89. instaui/arco/locales/zh_cn.py +224 -0
  90. instaui/arco/setup.py +36 -0
  91. instaui/arco/static/instaui-arco.css +1 -0
  92. instaui/arco/static/instaui-arco.js +55771 -0
  93. instaui/arco/types.py +24 -0
  94. instaui/boot_info.py +43 -0
  95. instaui/common/jsonable.py +37 -0
  96. instaui/components/__init__.py +0 -0
  97. instaui/components/column.py +26 -0
  98. instaui/components/component.py +47 -0
  99. instaui/components/content.py +34 -0
  100. instaui/components/directive.py +55 -0
  101. instaui/components/element.py +573 -0
  102. instaui/components/grid.py +213 -0
  103. instaui/components/html/__init__.py +49 -0
  104. instaui/components/html/_mixins.py +34 -0
  105. instaui/components/html/_preset.py +4 -0
  106. instaui/components/html/button.py +38 -0
  107. instaui/components/html/checkbox.py +35 -0
  108. instaui/components/html/date.py +28 -0
  109. instaui/components/html/div.py +7 -0
  110. instaui/components/html/form.py +7 -0
  111. instaui/components/html/heading.py +51 -0
  112. instaui/components/html/input.py +28 -0
  113. instaui/components/html/label.py +21 -0
  114. instaui/components/html/li.py +17 -0
  115. instaui/components/html/link.py +31 -0
  116. instaui/components/html/number.py +34 -0
  117. instaui/components/html/paragraph.py +29 -0
  118. instaui/components/html/range.py +48 -0
  119. instaui/components/html/select.py +69 -0
  120. instaui/components/html/span.py +19 -0
  121. instaui/components/html/table.py +36 -0
  122. instaui/components/html/textarea.py +28 -0
  123. instaui/components/html/ul.py +20 -0
  124. instaui/components/label.py +5 -0
  125. instaui/components/markdown/markdown.js +33 -0
  126. instaui/components/markdown/markdown.py +41 -0
  127. instaui/components/markdown/static/github-markdown.css +12 -0
  128. instaui/components/markdown/static/marked.esm.js +2579 -0
  129. instaui/components/match.py +108 -0
  130. instaui/components/row.py +17 -0
  131. instaui/components/shiki_code/shiki_code.js +126 -0
  132. instaui/components/shiki_code/shiki_code.py +99 -0
  133. instaui/components/shiki_code/static/langs/css.mjs +5 -0
  134. instaui/components/shiki_code/static/langs/markdown.mjs +5 -0
  135. instaui/components/shiki_code/static/langs/python.mjs +5 -0
  136. instaui/components/shiki_code/static/langs/shell.mjs +2 -0
  137. instaui/components/shiki_code/static/langs/shellscript.mjs +5 -0
  138. instaui/components/shiki_code/static/shiki-core.js +5784 -0
  139. instaui/components/shiki_code/static/shiki-style.css +179 -0
  140. instaui/components/shiki_code/static/shiki-transformers.js +461 -0
  141. instaui/components/shiki_code/static/themes/vitesse-dark.mjs +2 -0
  142. instaui/components/shiki_code/static/themes/vitesse-light.mjs +2 -0
  143. instaui/components/slot.py +81 -0
  144. instaui/components/transition_group.py +9 -0
  145. instaui/components/value_element.py +52 -0
  146. instaui/components/vfor.py +142 -0
  147. instaui/components/vif.py +42 -0
  148. instaui/consts.py +23 -0
  149. instaui/dependencies/component_dependency.py +22 -0
  150. instaui/dependencies/plugin_dependency.py +28 -0
  151. instaui/event/event_mixin.py +12 -0
  152. instaui/event/js_event.py +82 -0
  153. instaui/event/vue_event.py +66 -0
  154. instaui/event/web_event.py +123 -0
  155. instaui/experimental/__init__.py +3 -0
  156. instaui/experimental/debug.py +48 -0
  157. instaui/extra_libs/_echarts.py +3 -0
  158. instaui/extra_libs/_import_error.py +9 -0
  159. instaui/extra_libs/_mermaid.py +3 -0
  160. instaui/extra_libs/_shiki_code.py +3 -0
  161. instaui/fastapi_server/_utils.py +42 -0
  162. instaui/fastapi_server/_uvicorn.py +37 -0
  163. instaui/fastapi_server/debug_mode_router.py +60 -0
  164. instaui/fastapi_server/dependency_router.py +28 -0
  165. instaui/fastapi_server/event_router.py +58 -0
  166. instaui/fastapi_server/middlewares.py +19 -0
  167. instaui/fastapi_server/request_context.py +19 -0
  168. instaui/fastapi_server/resource.py +30 -0
  169. instaui/fastapi_server/server.py +308 -0
  170. instaui/fastapi_server/watch_router.py +53 -0
  171. instaui/handlers/_utils.py +88 -0
  172. instaui/handlers/event_handler.py +60 -0
  173. instaui/handlers/watch_handler.py +61 -0
  174. instaui/html_tools.py +94 -0
  175. instaui/inject.py +33 -0
  176. instaui/js/__init__.py +4 -0
  177. instaui/js/js_output.py +15 -0
  178. instaui/js/lambda_func.py +35 -0
  179. instaui/launch_collector.py +52 -0
  180. instaui/page_info.py +13 -0
  181. instaui/runtime/__init__.py +29 -0
  182. instaui/runtime/_app.py +234 -0
  183. instaui/runtime/_inner_helper.py +9 -0
  184. instaui/runtime/_link_manager.py +89 -0
  185. instaui/runtime/context.py +47 -0
  186. instaui/runtime/dataclass.py +30 -0
  187. instaui/runtime/resource.py +65 -0
  188. instaui/runtime/scope.py +133 -0
  189. instaui/runtime/ui_state_scope.py +15 -0
  190. instaui/settings/__init__.py +4 -0
  191. instaui/settings/__settings.py +13 -0
  192. instaui/shadcn_classless/_index.py +42 -0
  193. instaui/shadcn_classless/static/shadcn-classless.css +403 -0
  194. instaui/skip.py +12 -0
  195. instaui/spa_router/__init__.py +26 -0
  196. instaui/spa_router/_components.py +35 -0
  197. instaui/spa_router/_file_base_utils.py +273 -0
  198. instaui/spa_router/_functions.py +122 -0
  199. instaui/spa_router/_install.py +11 -0
  200. instaui/spa_router/_route_model.py +117 -0
  201. instaui/spa_router/_router_box.py +40 -0
  202. instaui/spa_router/_router_output.py +22 -0
  203. instaui/spa_router/_router_param_var.py +51 -0
  204. instaui/spa_router/_types.py +4 -0
  205. instaui/spa_router/templates/page_routes +60 -0
  206. instaui/static/insta-ui.css +1 -0
  207. instaui/static/insta-ui.esm-browser.prod.js +3717 -0
  208. instaui/static/insta-ui.ico +0 -0
  209. instaui/static/insta-ui.js.map +1 -0
  210. instaui/static/instaui-tools-browser.js +511 -0
  211. instaui/static/templates/debug/sse.html +117 -0
  212. instaui/static/templates/web.html +74 -0
  213. instaui/static/templates/webview.html +78 -0
  214. instaui/static/templates/zero.html +71 -0
  215. instaui/static/vue.esm-browser.prod.js +9 -0
  216. instaui/static/vue.global.prod.js +9 -0
  217. instaui/static/vue.runtime.esm-browser.prod.js +5 -0
  218. instaui/systems/file_system.py +6 -0
  219. instaui/systems/func_system.py +119 -0
  220. instaui/systems/js_system.py +22 -0
  221. instaui/systems/module_system.py +46 -0
  222. instaui/systems/pydantic_system.py +27 -0
  223. instaui/systems/string_system.py +10 -0
  224. instaui/tailwind/__init__.py +6 -0
  225. instaui/tailwind/_index.py +24 -0
  226. instaui/tailwind/static/tailwindcss-v3.min.js +62 -0
  227. instaui/tailwind/static/tailwindcss-v4.min.js +8 -0
  228. instaui/template/__init__.py +4 -0
  229. instaui/template/_utils.py +23 -0
  230. instaui/template/env.py +7 -0
  231. instaui/template/web_template.py +49 -0
  232. instaui/template/webview_template.py +48 -0
  233. instaui/template/zero_template.py +105 -0
  234. instaui/ui/__init__.py +144 -0
  235. instaui/ui/__init__.pyi +149 -0
  236. instaui/ui/events.py +25 -0
  237. instaui/ui_functions/input_slient_data.py +16 -0
  238. instaui/ui_functions/server.py +15 -0
  239. instaui/ui_functions/str_format.py +36 -0
  240. instaui/ui_functions/ui_page.py +16 -0
  241. instaui/ui_functions/ui_types.py +13 -0
  242. instaui/ui_functions/url_location.py +33 -0
  243. instaui/vars/_types.py +8 -0
  244. instaui/vars/data.py +68 -0
  245. instaui/vars/element_ref.py +40 -0
  246. instaui/vars/event_context.py +49 -0
  247. instaui/vars/event_extend.py +0 -0
  248. instaui/vars/js_computed.py +117 -0
  249. instaui/vars/mixin_types/common_type.py +5 -0
  250. instaui/vars/mixin_types/element_binding.py +16 -0
  251. instaui/vars/mixin_types/observable.py +7 -0
  252. instaui/vars/mixin_types/pathable.py +14 -0
  253. instaui/vars/mixin_types/py_binding.py +13 -0
  254. instaui/vars/mixin_types/str_format_binding.py +8 -0
  255. instaui/vars/mixin_types/var_type.py +5 -0
  256. instaui/vars/path_var.py +90 -0
  257. instaui/vars/ref.py +103 -0
  258. instaui/vars/slot_prop.py +46 -0
  259. instaui/vars/state.py +97 -0
  260. instaui/vars/types.py +24 -0
  261. instaui/vars/vfor_item.py +204 -0
  262. instaui/vars/vue_computed.py +81 -0
  263. instaui/vars/web_computed.py +209 -0
  264. instaui/vars/web_view_computed.py +1 -0
  265. instaui/version.py +3 -0
  266. instaui/watch/_types.py +4 -0
  267. instaui/watch/_utils.py +3 -0
  268. instaui/watch/js_watch.py +110 -0
  269. instaui/watch/vue_watch.py +77 -0
  270. instaui/watch/web_watch.py +181 -0
  271. instaui/webview/__init__.py +2 -0
  272. instaui/webview/_utils.py +8 -0
  273. instaui/webview/api.py +72 -0
  274. instaui/webview/func.py +114 -0
  275. instaui/webview/index.py +161 -0
  276. instaui/webview/resource.py +172 -0
  277. instaui/zero/__init__.py +3 -0
  278. instaui/zero/func.py +123 -0
  279. instaui/zero/scope.py +109 -0
  280. instaui-0.1.15.dist-info/METADATA +152 -0
  281. instaui-0.1.15.dist-info/RECORD +283 -0
  282. instaui-0.1.15.dist-info/WHEEL +5 -0
  283. 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,3 @@
1
+ from .debug import list_all_bindables
2
+
3
+ __all__ = ["list_all_bindables"]
@@ -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,3 @@
1
+ __all__ = ["echarts"]
2
+
3
+ from instaui_echarts import echarts # type: ignore
@@ -0,0 +1,9 @@
1
+ def show_error(msg: str):
2
+ def error_handler(error: Exception):
3
+ from instaui.components.html.paragraph import Paragraph
4
+
5
+ Paragraph(msg).style(
6
+ "border: 1px dashed red;padding: 1em;font-weight: bold;font-size: medium;"
7
+ )
8
+
9
+ return error_handler
@@ -0,0 +1,3 @@
1
+ __all__ = ["mermaid"]
2
+
3
+ from instaui_mermaid import Mermaid as mermaid # type: ignore
@@ -0,0 +1,3 @@
1
+ __all__ = ["code"]
2
+
3
+ from instaui_shiki import shiki as code # type: ignore
@@ -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