instaui 0.1.19__py2.py3-none-any.whl → 0.2.1__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/arco/component_types.py +389 -182
- instaui/arco/components/input.py +1 -1
- instaui/arco/components/typography.py +1 -1
- instaui/boot_info.py +2 -2
- instaui/components/component.py +9 -7
- instaui/components/element.py +11 -8
- instaui/components/html/button.py +2 -2
- instaui/components/html/input.py +2 -2
- instaui/components/html/range.py +2 -2
- instaui/components/html/select.py +3 -2
- instaui/components/html/textarea.py +2 -2
- instaui/components/html/ul.py +2 -2
- instaui/components/match.py +2 -2
- instaui/components/mixins.py +16 -0
- instaui/components/vfor.py +8 -6
- instaui/event/web_event.py +25 -2
- instaui/fastapi_server/_utils.py +5 -21
- instaui/fastapi_server/dependency_router.py +5 -1
- instaui/fastapi_server/event_router.py +5 -5
- instaui/fastapi_server/middlewares.py +12 -0
- instaui/fastapi_server/request_context.py +5 -4
- instaui/fastapi_server/server.py +5 -1
- instaui/fastapi_server/watch_router.py +2 -2
- instaui/js/fn.py +5 -1
- instaui/patch_update.py +54 -0
- instaui/pre_setup.py +45 -0
- instaui/response.py +64 -0
- instaui/runtime/_app.py +5 -1
- instaui/spa_router/_file_base_utils.py +1 -1
- instaui/static/insta-ui.esm-browser.prod.js +1400 -1391
- instaui/static/insta-ui.js.map +1 -1
- instaui/static/templates/web.html +5 -3
- instaui/static/templates/webview.html +4 -2
- instaui/static/templates/zero.html +4 -2
- instaui/template/web_template.py +1 -0
- instaui/template/webview_template.py +1 -0
- instaui/template/zero_template.py +1 -0
- instaui/ui/__init__.py +31 -6
- instaui/ui/__init__.pyi +4 -0
- instaui/ui_functions/server.py +10 -1
- instaui/ui_functions/ui_types.py +6 -2
- instaui/vars/mixin_types/element_binding.py +5 -1
- instaui/vars/mixin_types/observable.py +1 -1
- instaui/vars/vue_computed.py +5 -2
- instaui/watch/web_watch.py +11 -0
- instaui/webview/api.py +2 -23
- instaui/webview/resource.py +2 -0
- instaui/zero/func.py +2 -0
- {instaui-0.1.19.dist-info → instaui-0.2.1.dist-info}/METADATA +4 -3
- {instaui-0.1.19.dist-info → instaui-0.2.1.dist-info}/RECORD +52 -49
- instaui/webview/func.py +0 -114
- {instaui-0.1.19.dist-info → instaui-0.2.1.dist-info}/WHEEL +0 -0
- {instaui-0.1.19.dist-info → instaui-0.2.1.dist-info}/licenses/LICENSE +0 -0
instaui/arco/components/input.py
CHANGED
@@ -15,7 +15,7 @@ class Input(Element):
|
|
15
15
|
value: typing.Optional[TMaybeRef[str]] = None,
|
16
16
|
**kwargs: Unpack[component_types.TInput],
|
17
17
|
):
|
18
|
-
tag = f"a-input{'-'+ self._exten_name if self._exten_name else ''}"
|
18
|
+
tag = f"a-input{'-' + self._exten_name if self._exten_name else ''}"
|
19
19
|
super().__init__(tag)
|
20
20
|
try_setup_vmodel(self, value)
|
21
21
|
self.props(handle_props(kwargs)) # type: ignore
|
@@ -17,7 +17,7 @@ class Typography(Element):
|
|
17
17
|
text: typing.Optional[TMaybeRef[str]] = None,
|
18
18
|
**kwargs: Unpack[component_types.TTypography],
|
19
19
|
):
|
20
|
-
tag = f"a-typography{'-'+ self._exten_name if self._exten_name else ''}"
|
20
|
+
tag = f"a-typography{'-' + self._exten_name if self._exten_name else ''}"
|
21
21
|
super().__init__(tag)
|
22
22
|
|
23
23
|
if text is not None:
|
instaui/boot_info.py
CHANGED
@@ -11,14 +11,14 @@ _colors = {
|
|
11
11
|
|
12
12
|
|
13
13
|
def zero_boot_info(file: Path):
|
14
|
-
message = f"""{_app_message(
|
14
|
+
message = f"""{_app_message("zero")}
|
15
15
|
{_arrow_right()}file: {_with_color(str(file))}
|
16
16
|
"""
|
17
17
|
print(message)
|
18
18
|
|
19
19
|
|
20
20
|
def web_boot_info(ip: str):
|
21
|
-
message = f"""{_app_message(
|
21
|
+
message = f"""{_app_message("web")}
|
22
22
|
{_arrow_right()}Local: {_with_color(str(ip), "blue")}
|
23
23
|
"""
|
24
24
|
print(message)
|
instaui/components/component.py
CHANGED
@@ -11,16 +11,19 @@ from instaui.runtime import (
|
|
11
11
|
check_default_app_slot_or_error,
|
12
12
|
)
|
13
13
|
from instaui.components.slot import SlotManager
|
14
|
-
from instaui.vars.mixin_types.element_binding import
|
14
|
+
from instaui.vars.mixin_types.element_binding import (
|
15
|
+
ElementBindingMixin,
|
16
|
+
ElementBindingProtocol,
|
17
|
+
)
|
15
18
|
|
16
19
|
|
17
20
|
class Component(Jsonable):
|
18
|
-
def __init__(self, tag: Optional[Union[str,
|
21
|
+
def __init__(self, tag: Optional[Union[str, ElementBindingProtocol]] = None):
|
19
22
|
check_default_app_slot_or_error(
|
20
23
|
"Not allowed to create element outside of ui.page"
|
21
24
|
)
|
22
25
|
|
23
|
-
self.
|
26
|
+
self._tag = (
|
24
27
|
"div"
|
25
28
|
if tag is None or tag == ""
|
26
29
|
else (
|
@@ -41,7 +44,6 @@ class Component(Jsonable):
|
|
41
44
|
self._slot_manager.default.__exit__(*_)
|
42
45
|
|
43
46
|
def _to_json_dict(self) -> Dict:
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
return data
|
47
|
+
return {
|
48
|
+
"tag": self._tag,
|
49
|
+
}
|
instaui/components/element.py
CHANGED
@@ -34,7 +34,10 @@ from .slot import SlotManager, Slot
|
|
34
34
|
from instaui import consts
|
35
35
|
from instaui.components.component import Component
|
36
36
|
|
37
|
-
from instaui.vars.mixin_types.element_binding import
|
37
|
+
from instaui.vars.mixin_types.element_binding import (
|
38
|
+
ElementBindingMixin,
|
39
|
+
ElementBindingProtocol,
|
40
|
+
)
|
38
41
|
|
39
42
|
if TYPE_CHECKING:
|
40
43
|
from instaui.event.event_mixin import EventMixin
|
@@ -79,7 +82,7 @@ class Element(Component):
|
|
79
82
|
_default_classes: ClassVar[List[str]] = []
|
80
83
|
_default_style: ClassVar[Dict[str, str]] = {}
|
81
84
|
|
82
|
-
def __init__(self, tag: Optional[Union[str,
|
85
|
+
def __init__(self, tag: Optional[Union[str, ElementBindingProtocol]] = None):
|
83
86
|
if self.dependency:
|
84
87
|
tag = self.dependency.tag_name or ""
|
85
88
|
|
@@ -100,7 +103,7 @@ class Element(Component):
|
|
100
103
|
self._directives: Dict[Directive, None] = {}
|
101
104
|
|
102
105
|
self._slot_manager = SlotManager()
|
103
|
-
self.
|
106
|
+
self.__element_ref: Optional[ElementRef] = None
|
104
107
|
|
105
108
|
def __init_subclass__(
|
106
109
|
cls,
|
@@ -336,7 +339,7 @@ class Element(Component):
|
|
336
339
|
self._directives[directive] = None
|
337
340
|
return self
|
338
341
|
|
339
|
-
def display(self, value: Union[
|
342
|
+
def display(self, value: Union[ElementBindingProtocol, bool]) -> Self:
|
340
343
|
return self.directive(Directive(is_sys=False, name="vshow", value=value))
|
341
344
|
|
342
345
|
def event_dataset(self, data: Any, name: str = "event-data") -> Self:
|
@@ -347,7 +350,7 @@ class Element(Component):
|
|
347
350
|
return self
|
348
351
|
|
349
352
|
def element_ref(self, ref: ElementRef):
|
350
|
-
self.
|
353
|
+
self.__element_ref = ref
|
351
354
|
return self
|
352
355
|
|
353
356
|
def update_dependencies(
|
@@ -464,10 +467,10 @@ class Element(Component):
|
|
464
467
|
app_slot.get_temp_component_dependency(tag_name, self.dependency)
|
465
468
|
)
|
466
469
|
|
467
|
-
if self.
|
470
|
+
if self.__element_ref:
|
468
471
|
scope = get_current_scope()
|
469
|
-
data["eRef"] = self.
|
470
|
-
scope.register_element_ref(self.
|
472
|
+
data["eRef"] = self.__element_ref._to_element_config()
|
473
|
+
scope.register_element_ref(self.__element_ref)
|
471
474
|
|
472
475
|
return data
|
473
476
|
|
@@ -5,15 +5,15 @@ from typing import (
|
|
5
5
|
Optional,
|
6
6
|
)
|
7
7
|
from instaui.components.element import Element
|
8
|
-
|
9
8
|
from instaui.event.event_mixin import EventMixin
|
10
9
|
from instaui.vars.types import TMaybeRef
|
10
|
+
from instaui.components.mixins import CanDisabledMixin
|
11
11
|
|
12
12
|
if TYPE_CHECKING:
|
13
13
|
pass
|
14
14
|
|
15
15
|
|
16
|
-
class Button(Element):
|
16
|
+
class Button(Element, CanDisabledMixin):
|
17
17
|
def __init__(
|
18
18
|
self,
|
19
19
|
text: Optional[TMaybeRef[str]] = None,
|
instaui/components/html/input.py
CHANGED
@@ -2,14 +2,14 @@ from __future__ import annotations
|
|
2
2
|
from typing import TYPE_CHECKING, Optional, Union
|
3
3
|
from instaui.components.element import Element
|
4
4
|
from instaui.components.value_element import ValueElement
|
5
|
-
|
5
|
+
from instaui.components.mixins import CanDisabledMixin
|
6
6
|
from ._mixins import InputEventMixin
|
7
7
|
|
8
8
|
if TYPE_CHECKING:
|
9
9
|
from instaui.vars.types import TMaybeRef
|
10
10
|
|
11
11
|
|
12
|
-
class Input(InputEventMixin, ValueElement[str]):
|
12
|
+
class Input(InputEventMixin, CanDisabledMixin, ValueElement[str]):
|
13
13
|
def __init__(
|
14
14
|
self,
|
15
15
|
value: Union[str, TMaybeRef[str], None] = None,
|
instaui/components/html/range.py
CHANGED
@@ -4,7 +4,7 @@ from instaui.components.element import Element
|
|
4
4
|
from instaui.components.value_element import ValueElement
|
5
5
|
from instaui import consts
|
6
6
|
from instaui.vars.types import TMaybeRef
|
7
|
-
from instaui.vars.mixin_types.element_binding import
|
7
|
+
from instaui.vars.mixin_types.element_binding import ElementBindingProtocol
|
8
8
|
from ._mixins import InputEventMixin
|
9
9
|
|
10
10
|
_T_value = Union[int, float]
|
@@ -31,7 +31,7 @@ class Range(InputEventMixin, ValueElement[_T_value]):
|
|
31
31
|
|
32
32
|
def vmodel(
|
33
33
|
self,
|
34
|
-
value:
|
34
|
+
value: ElementBindingProtocol,
|
35
35
|
modifiers: Union[consts.TModifier, List[consts.TModifier], None] = None,
|
36
36
|
*,
|
37
37
|
prop_name: str = "value",
|
@@ -9,11 +9,12 @@ from instaui.components.element import Element
|
|
9
9
|
from instaui.event.event_mixin import EventMixin
|
10
10
|
from instaui.vars.types import TMaybeRef
|
11
11
|
from instaui.components.vfor import VFor
|
12
|
+
from instaui.components.mixins import CanDisabledMixin
|
12
13
|
|
13
14
|
_T_Select_Value = Union[List[str], str]
|
14
15
|
|
15
16
|
|
16
|
-
class Select(ValueElement[Union[List[str], str]]):
|
17
|
+
class Select(CanDisabledMixin, ValueElement[Union[List[str], str]]):
|
17
18
|
def __init__(
|
18
19
|
self,
|
19
20
|
value: Union[_T_Select_Value, TMaybeRef[_T_Select_Value], None] = None,
|
@@ -48,7 +49,7 @@ class Select(ValueElement[Union[List[str], str]]):
|
|
48
49
|
|
49
50
|
return select
|
50
51
|
|
51
|
-
class Option(Element):
|
52
|
+
class Option(Element, CanDisabledMixin):
|
52
53
|
def __init__(
|
53
54
|
self,
|
54
55
|
text: Optional[TMaybeRef[str]] = None,
|
@@ -2,14 +2,14 @@ from __future__ import annotations
|
|
2
2
|
from typing import TYPE_CHECKING, Optional, Union
|
3
3
|
from instaui.components.element import Element
|
4
4
|
from instaui.components.value_element import ValueElement
|
5
|
-
|
5
|
+
from instaui.components.mixins import CanDisabledMixin
|
6
6
|
from ._mixins import InputEventMixin
|
7
7
|
|
8
8
|
if TYPE_CHECKING:
|
9
9
|
from instaui.vars.types import TMaybeRef
|
10
10
|
|
11
11
|
|
12
|
-
class Textarea(InputEventMixin, ValueElement[str]):
|
12
|
+
class Textarea(InputEventMixin, CanDisabledMixin, ValueElement[str]):
|
13
13
|
def __init__(
|
14
14
|
self,
|
15
15
|
value: Union[str, TMaybeRef[str], None] = None,
|
instaui/components/html/ul.py
CHANGED
@@ -4,7 +4,7 @@ from instaui.components.element import Element
|
|
4
4
|
from .li import Li
|
5
5
|
from instaui.components.vfor import VFor
|
6
6
|
|
7
|
-
from instaui.vars.mixin_types.element_binding import
|
7
|
+
from instaui.vars.mixin_types.element_binding import ElementBindingProtocol
|
8
8
|
|
9
9
|
|
10
10
|
class Ul(Element):
|
@@ -12,7 +12,7 @@ class Ul(Element):
|
|
12
12
|
super().__init__("ul")
|
13
13
|
|
14
14
|
@classmethod
|
15
|
-
def from_list(cls, data: Union[List,
|
15
|
+
def from_list(cls, data: Union[List, ElementBindingProtocol]) -> Ul:
|
16
16
|
with Ul() as ul:
|
17
17
|
with VFor(data) as items:
|
18
18
|
Li(items)
|
instaui/components/match.py
CHANGED
@@ -4,11 +4,11 @@ import typing
|
|
4
4
|
from instaui.components.component import Component
|
5
5
|
from instaui.runtime._app import new_scope
|
6
6
|
|
7
|
-
from instaui.vars.mixin_types.element_binding import
|
7
|
+
from instaui.vars.mixin_types.element_binding import ElementBindingProtocol
|
8
8
|
|
9
9
|
|
10
10
|
class Match(Component):
|
11
|
-
def __init__(self, on:
|
11
|
+
def __init__(self, on: ElementBindingProtocol):
|
12
12
|
super().__init__("match")
|
13
13
|
self._on = on
|
14
14
|
self._default_case = None
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Any, Dict, Protocol, TYPE_CHECKING, Union
|
3
|
+
from typing_extensions import Self
|
4
|
+
from instaui.vars.types import TMaybeRef
|
5
|
+
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
from instaui.components.element import Element
|
8
|
+
|
9
|
+
|
10
|
+
class PropsProtocol(Protocol):
|
11
|
+
def props(self, add: Union[str, Dict[str, Any], TMaybeRef]) -> Self: ...
|
12
|
+
|
13
|
+
|
14
|
+
class CanDisabledMixin:
|
15
|
+
def disabled(self: PropsProtocol, disabled: TMaybeRef[bool] = True) -> Element:
|
16
|
+
return self.props({"disabled": disabled}) # type: ignore
|
instaui/components/vfor.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from typing import (
|
3
3
|
Dict,
|
4
|
-
List,
|
5
4
|
Literal,
|
6
5
|
Mapping,
|
7
6
|
Optional,
|
@@ -17,7 +16,10 @@ from instaui.components.component import Component
|
|
17
16
|
from instaui.vars.vfor_item import VForItem, VForDict, VForWithIndex
|
18
17
|
from instaui.runtime._app import get_app_slot, new_scope
|
19
18
|
|
20
|
-
from instaui.vars.mixin_types.element_binding import
|
19
|
+
from instaui.vars.mixin_types.element_binding import (
|
20
|
+
ElementBindingMixin,
|
21
|
+
ElementBindingProtocol,
|
22
|
+
)
|
21
23
|
|
22
24
|
_T = TypeVar("_T")
|
23
25
|
|
@@ -25,7 +27,7 @@ _T = TypeVar("_T")
|
|
25
27
|
class VFor(Component, Generic[_T]):
|
26
28
|
def __init__(
|
27
29
|
self,
|
28
|
-
data: Union[Sequence[_T],
|
30
|
+
data: Union[Sequence[_T], ElementBindingProtocol],
|
29
31
|
*,
|
30
32
|
key: Union[Literal["item", "index"], str] = "index",
|
31
33
|
):
|
@@ -117,10 +119,10 @@ class VFor(Component, Generic[_T]):
|
|
117
119
|
|
118
120
|
@overload
|
119
121
|
@classmethod
|
120
|
-
def range(cls, end:
|
122
|
+
def range(cls, end: ElementBindingProtocol) -> VFor[int]: ...
|
121
123
|
|
122
124
|
@classmethod
|
123
|
-
def range(cls, end: Union[int,
|
125
|
+
def range(cls, end: Union[int, ElementBindingProtocol]) -> VFor[int]:
|
124
126
|
obj = cls(None) # type: ignore
|
125
127
|
|
126
128
|
num = ( # type: ignore
|
@@ -135,6 +137,6 @@ class VFor(Component, Generic[_T]):
|
|
135
137
|
|
136
138
|
@classmethod
|
137
139
|
def from_dict(
|
138
|
-
cls, data: Union[Mapping, pydantic.BaseModel,
|
140
|
+
cls, data: Union[Mapping, pydantic.BaseModel, ElementBindingProtocol]
|
139
141
|
):
|
140
142
|
return VForDict(VFor(data)) # type: ignore
|
instaui/event/web_event.py
CHANGED
@@ -5,15 +5,15 @@ from instaui.common.jsonable import Jsonable
|
|
5
5
|
from instaui.runtime._app import get_current_scope, get_app_slot
|
6
6
|
from instaui.vars.mixin_types.py_binding import CanInputMixin, CanOutputMixin
|
7
7
|
from instaui.handlers import event_handler
|
8
|
+
from instaui import pre_setup as _pre_setup
|
8
9
|
from .event_mixin import EventMixin
|
9
10
|
|
11
|
+
|
10
12
|
_SYNC_TYPE = "sync"
|
11
13
|
_ASYNC_TYPE = "async"
|
12
14
|
|
13
15
|
P = ParamSpec("P")
|
14
16
|
R = typing.TypeVar("R")
|
15
|
-
_T_input = typing.TypeVar("_T_input")
|
16
|
-
_T_output = typing.TypeVar("_T_output")
|
17
17
|
|
18
18
|
|
19
19
|
class WebEvent(Jsonable, EventMixin, typing.Generic[P, R]):
|
@@ -22,10 +22,15 @@ class WebEvent(Jsonable, EventMixin, typing.Generic[P, R]):
|
|
22
22
|
fn: typing.Callable[P, R],
|
23
23
|
inputs: typing.Sequence[CanInputMixin],
|
24
24
|
outputs: typing.Sequence[CanOutputMixin],
|
25
|
+
pre_setup: typing.Optional[typing.Dict] = None,
|
25
26
|
):
|
27
|
+
if pre_setup:
|
28
|
+
_pre_setup._check_args(pre_setup)
|
29
|
+
|
26
30
|
self._inputs = inputs
|
27
31
|
self._outputs = outputs
|
28
32
|
self._fn = fn
|
33
|
+
self._pre_setup = pre_setup
|
29
34
|
|
30
35
|
scope = get_current_scope()
|
31
36
|
self._sid = scope.id
|
@@ -73,6 +78,9 @@ class WebEvent(Jsonable, EventMixin, typing.Generic[P, R]):
|
|
73
78
|
if self._outputs:
|
74
79
|
data["set"] = [ref._to_output_config() for ref in self._outputs]
|
75
80
|
|
81
|
+
if self._pre_setup:
|
82
|
+
data["preSetup"] = _pre_setup.convert_config(self._pre_setup)
|
83
|
+
|
76
84
|
return data
|
77
85
|
|
78
86
|
|
@@ -80,6 +88,7 @@ def event(
|
|
80
88
|
*,
|
81
89
|
inputs: typing.Optional[typing.Sequence] = None,
|
82
90
|
outputs: typing.Optional[typing.Sequence] = None,
|
91
|
+
pre_setup: typing.Optional[typing.Dict] = None,
|
83
92
|
):
|
84
93
|
"""
|
85
94
|
Creates an event handler decorator for binding reactive logic to component events.
|
@@ -92,6 +101,8 @@ def event(
|
|
92
101
|
outputs (typing.Optional[typing.Sequence], optional): Targets (state variables, UI elements) that should
|
93
102
|
update when this handler executes. Used for coordinating
|
94
103
|
interface updates after the event is processed.
|
104
|
+
pre_setup (typing.Optional[typing.Dict], optional): A dictionary of pre-setup actions to be executed before the event executes.
|
105
|
+
|
95
106
|
|
96
107
|
# Example:
|
97
108
|
.. code-block:: python
|
@@ -106,6 +117,17 @@ def event(
|
|
106
117
|
html.button("click me").on_click(plus_one)
|
107
118
|
html.paragraph(a)
|
108
119
|
|
120
|
+
use pre_setup:
|
121
|
+
.. code-block:: python
|
122
|
+
a = ui.state(0)
|
123
|
+
task_running = ui.state(False)
|
124
|
+
|
125
|
+
@ui.event(inputs=[a], outputs=[a], pre_setup={task_running: True})
|
126
|
+
async def long_running_task(a):
|
127
|
+
await asyncio.sleep(3)
|
128
|
+
return a + 1
|
129
|
+
|
130
|
+
html.button("click me").on_click(long_running_task).disabled(task_running)
|
109
131
|
"""
|
110
132
|
|
111
133
|
def wrapper(func: typing.Callable[P, R]):
|
@@ -113,6 +135,7 @@ def event(
|
|
113
135
|
func,
|
114
136
|
inputs or [],
|
115
137
|
outputs=outputs or [],
|
138
|
+
pre_setup=pre_setup,
|
116
139
|
)
|
117
140
|
|
118
141
|
return wrapper
|
instaui/fastapi_server/_utils.py
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
from typing import Any, Dict, List, Optional
|
1
|
+
from typing import Any, Dict, List, Optional, Sequence
|
2
2
|
from instaui.runtime._app import get_app_slot
|
3
|
-
from instaui.
|
3
|
+
from instaui.response import response_data
|
4
4
|
import pydantic
|
5
5
|
|
6
6
|
|
7
7
|
class ResponseData(pydantic.BaseModel):
|
8
8
|
values: Optional[List[Any]] = None
|
9
|
-
|
9
|
+
types: Optional[Sequence[int]] = None
|
10
10
|
|
11
11
|
|
12
12
|
def update_app_page_info(data: Dict):
|
@@ -22,21 +22,5 @@ def update_app_page_info(data: Dict):
|
|
22
22
|
app._query_params = page_info["queryParams"]
|
23
23
|
|
24
24
|
|
25
|
-
def
|
26
|
-
|
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
|
25
|
+
def response_web_data(outputs_binding_count: int, result: Any):
|
26
|
+
return ResponseData(**response_data(outputs_binding_count, result))
|
@@ -16,7 +16,11 @@ def _dependency_handler(app: FastAPI):
|
|
16
16
|
def _(hash_part: str, file_name: str) -> FileResponse:
|
17
17
|
hash_part_with_extend_paths = hash_part.split("/", maxsplit=1)
|
18
18
|
hash_part = hash_part_with_extend_paths[0]
|
19
|
-
extend_path =
|
19
|
+
extend_path = (
|
20
|
+
None
|
21
|
+
if len(hash_part_with_extend_paths) == 1
|
22
|
+
else hash_part_with_extend_paths[1]
|
23
|
+
)
|
20
24
|
|
21
25
|
folder = resource.get_folder_path(hash_part)
|
22
26
|
if extend_path:
|
@@ -20,14 +20,14 @@ def _async_handler(app: FastAPI):
|
|
20
20
|
if handler is None:
|
21
21
|
return {"error": "event handler not found"}
|
22
22
|
|
23
|
-
assert inspect.iscoroutinefunction(
|
24
|
-
handler
|
25
|
-
)
|
23
|
+
assert inspect.iscoroutinefunction(handler.fn), (
|
24
|
+
"handler must be a coroutine function"
|
25
|
+
)
|
26
26
|
|
27
27
|
_utils.update_app_page_info(data)
|
28
28
|
|
29
29
|
result = await handler.fn(*handler.get_handler_args(_get_binds_from_data(data)))
|
30
|
-
return _utils.
|
30
|
+
return _utils.response_web_data(handler.outputs_binding_count, result)
|
31
31
|
|
32
32
|
|
33
33
|
def _sync_handler(app: FastAPI):
|
@@ -41,7 +41,7 @@ def _sync_handler(app: FastAPI):
|
|
41
41
|
|
42
42
|
result = handler.fn(*handler.get_handler_args(_get_binds_from_data(data)))
|
43
43
|
|
44
|
-
return _utils.
|
44
|
+
return _utils.response_web_data(handler.outputs_binding_count, result)
|
45
45
|
|
46
46
|
if get_context().debug_mode:
|
47
47
|
|
@@ -17,3 +17,15 @@ class RequestContextMiddleware(BaseHTTPMiddleware):
|
|
17
17
|
reset_app_slot(system_slot_token)
|
18
18
|
|
19
19
|
return response
|
20
|
+
|
21
|
+
|
22
|
+
class NoCacheDebugModeMiddleware(BaseHTTPMiddleware):
|
23
|
+
async def dispatch(self, request: Request, call_next: Callable) -> Any:
|
24
|
+
response = await call_next(request)
|
25
|
+
|
26
|
+
if request.url.path.endswith((".js", ".css")):
|
27
|
+
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
28
|
+
response.headers["Pragma"] = "no-cache"
|
29
|
+
response.headers["Expires"] = "0"
|
30
|
+
|
31
|
+
return response
|
@@ -3,17 +3,18 @@ from typing import Optional
|
|
3
3
|
from fastapi import Request
|
4
4
|
from contextvars import ContextVar, Token
|
5
5
|
|
6
|
-
current_request_ctx
|
6
|
+
current_request_ctx: ContextVar[Optional[Request]] = ContextVar(
|
7
|
+
"current_request", default=None
|
8
|
+
)
|
7
9
|
|
8
10
|
|
9
|
-
|
10
|
-
def set_current_request(request: Request) :
|
11
|
+
def set_current_request(request: Request):
|
11
12
|
return current_request_ctx.set(request)
|
12
13
|
|
14
|
+
|
13
15
|
def reset_current_request(token: Token) -> None:
|
14
16
|
current_request_ctx.reset(token)
|
15
17
|
|
16
18
|
|
17
19
|
def get_current_request() -> Request:
|
18
20
|
return current_request_ctx.get() # type: ignore
|
19
|
-
|
instaui/fastapi_server/server.py
CHANGED
@@ -22,6 +22,7 @@ from instaui.page_info import PageInfo
|
|
22
22
|
|
23
23
|
from instaui import consts
|
24
24
|
from instaui.runtime._app import get_app_slot, get_default_app_slot
|
25
|
+
from instaui.runtime.context import get_context
|
25
26
|
from instaui.runtime.dataclass import JsLink, VueAppComponent
|
26
27
|
from instaui.template import web_template
|
27
28
|
|
@@ -30,7 +31,7 @@ from . import dependency_router
|
|
30
31
|
from . import event_router
|
31
32
|
from . import watch_router
|
32
33
|
from . import debug_mode_router
|
33
|
-
from .middlewares import RequestContextMiddleware
|
34
|
+
from .middlewares import RequestContextMiddleware, NoCacheDebugModeMiddleware
|
34
35
|
from ._uvicorn import UvicornServer
|
35
36
|
from . import resource
|
36
37
|
from instaui.version import __version__ as _INSTA_VERSION
|
@@ -55,6 +56,8 @@ class Server:
|
|
55
56
|
):
|
56
57
|
self.app = FastAPI()
|
57
58
|
self.app.add_middleware(RequestContextMiddleware)
|
59
|
+
if get_context().debug_mode:
|
60
|
+
self.app.add_middleware(NoCacheDebugModeMiddleware)
|
58
61
|
|
59
62
|
if use_gzip:
|
60
63
|
self.app.add_middleware(
|
@@ -130,6 +133,7 @@ class Server:
|
|
130
133
|
favicon_url = resource.record_resource(default_html_resource.favicon)
|
131
134
|
|
132
135
|
model = web_template.WebTemplateModel(
|
136
|
+
version=_INSTA_VERSION,
|
133
137
|
vue_js_link=VUE_JS_HASH_LINK,
|
134
138
|
instaui_js_link=INSTAUI_JS_HASH_LINK,
|
135
139
|
css_links=[
|
@@ -24,7 +24,7 @@ def _async_handler(app: FastAPI):
|
|
24
24
|
result = await handler_info.fn(
|
25
25
|
*handler_info.get_handler_args(_get_binds_from_data(data))
|
26
26
|
)
|
27
|
-
return _utils.
|
27
|
+
return _utils.response_web_data(handler_info.outputs_binding_count, result)
|
28
28
|
|
29
29
|
|
30
30
|
def _sync_handler(app: FastAPI):
|
@@ -40,7 +40,7 @@ def _sync_handler(app: FastAPI):
|
|
40
40
|
result = handler_info.fn(
|
41
41
|
*handler_info.get_handler_args(_get_binds_from_data(data))
|
42
42
|
)
|
43
|
-
return _utils.
|
43
|
+
return _utils.response_web_data(handler_info.outputs_binding_count, result)
|
44
44
|
|
45
45
|
if get_context().debug_mode:
|
46
46
|
|
instaui/js/fn.py
CHANGED
@@ -21,12 +21,13 @@ class JsFn(Jsonable, CanInputMixin):
|
|
21
21
|
ui.label(result)
|
22
22
|
"""
|
23
23
|
|
24
|
-
def __init__(self, code: str):
|
24
|
+
def __init__(self, code: str, *, execute_immediately=False):
|
25
25
|
self.code = code
|
26
26
|
self.__type = "jsFn"
|
27
27
|
app = get_app_slot()
|
28
28
|
app.register_js_fn(self)
|
29
29
|
self.__id = app.generate_js_fn_id()
|
30
|
+
self._execute_immediately = execute_immediately
|
30
31
|
|
31
32
|
def _to_input_config(self):
|
32
33
|
return {
|
@@ -39,4 +40,7 @@ class JsFn(Jsonable, CanInputMixin):
|
|
39
40
|
data["type"] = self.__type
|
40
41
|
data["id"] = self.__id
|
41
42
|
|
43
|
+
if self._execute_immediately is True:
|
44
|
+
data["immediately"] = 1
|
45
|
+
|
42
46
|
return data
|
instaui/patch_update.py
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
from typing import Any, Iterable, Sequence, Union
|
2
|
+
from instaui.common.jsonable import Jsonable
|
3
|
+
|
4
|
+
|
5
|
+
class PatchSetRecord:
|
6
|
+
def __init__(self, *, path: Sequence[Union[str, int]], value: Any):
|
7
|
+
self.path = list(path)
|
8
|
+
self.value = value
|
9
|
+
|
10
|
+
|
11
|
+
class PatchSet(Jsonable):
|
12
|
+
def __init__(self, records: Iterable[PatchSetRecord]):
|
13
|
+
self._records = list(records)
|
14
|
+
|
15
|
+
def patch_set(self, *, path: Sequence[Union[str, int]], value: Any):
|
16
|
+
return PatchSet([*self._records, PatchSetRecord(path=path, value=value)])
|
17
|
+
|
18
|
+
def _to_json_dict(self):
|
19
|
+
return [[r.path, r.value] for r in self._records]
|
20
|
+
|
21
|
+
|
22
|
+
def patch_set(*, path: Sequence[Union[str, int]], value: Any):
|
23
|
+
"""
|
24
|
+
Generates a patch to specify how data should be updated.
|
25
|
+
|
26
|
+
This function is typically used within the return value of functions
|
27
|
+
used in `ui.event` or `ui.watch`, indicating the way data modifications are applied.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
path (Sequence[Union[str, int]]): A sequence representing the path to the item that needs to be updated.
|
31
|
+
Each element can either be a string (for dictionary keys) or an integer
|
32
|
+
(for list indices).
|
33
|
+
value (Any): The new value to set at the specified path. Can be of any type.
|
34
|
+
|
35
|
+
Example:
|
36
|
+
.. code-block:: python
|
37
|
+
|
38
|
+
data = ui.state(
|
39
|
+
{
|
40
|
+
"v1": ["a", "b", "c"],
|
41
|
+
"v2": ["x", "y", "z"],
|
42
|
+
}
|
43
|
+
)
|
44
|
+
|
45
|
+
@ui.event(outputs=[data])
|
46
|
+
def update_data():
|
47
|
+
# update the second element of "v1" to "foo"
|
48
|
+
return ui.patch_set(path=["v1", 1], value="foo")
|
49
|
+
|
50
|
+
html.button("update data").on_click(update_data)
|
51
|
+
ui.label(data)
|
52
|
+
"""
|
53
|
+
|
54
|
+
return PatchSet([PatchSetRecord(path=path, value=value)])
|