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.
Files changed (53) hide show
  1. instaui/arco/component_types.py +389 -182
  2. instaui/arco/components/input.py +1 -1
  3. instaui/arco/components/typography.py +1 -1
  4. instaui/boot_info.py +2 -2
  5. instaui/components/component.py +9 -7
  6. instaui/components/element.py +11 -8
  7. instaui/components/html/button.py +2 -2
  8. instaui/components/html/input.py +2 -2
  9. instaui/components/html/range.py +2 -2
  10. instaui/components/html/select.py +3 -2
  11. instaui/components/html/textarea.py +2 -2
  12. instaui/components/html/ul.py +2 -2
  13. instaui/components/match.py +2 -2
  14. instaui/components/mixins.py +16 -0
  15. instaui/components/vfor.py +8 -6
  16. instaui/event/web_event.py +25 -2
  17. instaui/fastapi_server/_utils.py +5 -21
  18. instaui/fastapi_server/dependency_router.py +5 -1
  19. instaui/fastapi_server/event_router.py +5 -5
  20. instaui/fastapi_server/middlewares.py +12 -0
  21. instaui/fastapi_server/request_context.py +5 -4
  22. instaui/fastapi_server/server.py +5 -1
  23. instaui/fastapi_server/watch_router.py +2 -2
  24. instaui/js/fn.py +5 -1
  25. instaui/patch_update.py +54 -0
  26. instaui/pre_setup.py +45 -0
  27. instaui/response.py +64 -0
  28. instaui/runtime/_app.py +5 -1
  29. instaui/spa_router/_file_base_utils.py +1 -1
  30. instaui/static/insta-ui.esm-browser.prod.js +1400 -1391
  31. instaui/static/insta-ui.js.map +1 -1
  32. instaui/static/templates/web.html +5 -3
  33. instaui/static/templates/webview.html +4 -2
  34. instaui/static/templates/zero.html +4 -2
  35. instaui/template/web_template.py +1 -0
  36. instaui/template/webview_template.py +1 -0
  37. instaui/template/zero_template.py +1 -0
  38. instaui/ui/__init__.py +31 -6
  39. instaui/ui/__init__.pyi +4 -0
  40. instaui/ui_functions/server.py +10 -1
  41. instaui/ui_functions/ui_types.py +6 -2
  42. instaui/vars/mixin_types/element_binding.py +5 -1
  43. instaui/vars/mixin_types/observable.py +1 -1
  44. instaui/vars/vue_computed.py +5 -2
  45. instaui/watch/web_watch.py +11 -0
  46. instaui/webview/api.py +2 -23
  47. instaui/webview/resource.py +2 -0
  48. instaui/zero/func.py +2 -0
  49. {instaui-0.1.19.dist-info → instaui-0.2.1.dist-info}/METADATA +4 -3
  50. {instaui-0.1.19.dist-info → instaui-0.2.1.dist-info}/RECORD +52 -49
  51. instaui/webview/func.py +0 -114
  52. {instaui-0.1.19.dist-info → instaui-0.2.1.dist-info}/WHEEL +0 -0
  53. {instaui-0.1.19.dist-info → instaui-0.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -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('zero')}
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('web')}
21
+ message = f"""{_app_message("web")}
22
22
  {_arrow_right()}Local: {_with_color(str(ip), "blue")}
23
23
  """
24
24
  print(message)
@@ -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 ElementBindingMixin
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, ElementBindingMixin]] = None):
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.tag = (
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
- data = super()._to_json_dict()
45
- data["tag"] = self.tag
46
-
47
- return data
47
+ return {
48
+ "tag": self._tag,
49
+ }
@@ -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 ElementBindingMixin
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, ElementBindingMixin]] = None):
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._element_ref: Optional[ElementRef] = None
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[ElementBindingMixin[bool], bool]) -> Self:
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._element_ref = ref
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._element_ref:
470
+ if self.__element_ref:
468
471
  scope = get_current_scope()
469
- data["eRef"] = self._element_ref._to_element_config()
470
- scope.register_element_ref(self._element_ref)
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,
@@ -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,
@@ -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 ElementBindingMixin
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: ElementBindingMixin,
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,
@@ -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 ElementBindingMixin
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, ElementBindingMixin[List]]) -> Ul:
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)
@@ -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 ElementBindingMixin
7
+ from instaui.vars.mixin_types.element_binding import ElementBindingProtocol
8
8
 
9
9
 
10
10
  class Match(Component):
11
- def __init__(self, on: ElementBindingMixin):
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
@@ -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 ElementBindingMixin
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], ElementBindingMixin[List[_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: ElementBindingMixin[int]) -> VFor[int]: ...
122
+ def range(cls, end: ElementBindingProtocol) -> VFor[int]: ...
121
123
 
122
124
  @classmethod
123
- def range(cls, end: Union[int, ElementBindingMixin[int]]) -> VFor[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, ElementBindingMixin[Dict]]
140
+ cls, data: Union[Mapping, pydantic.BaseModel, ElementBindingProtocol]
139
141
  ):
140
142
  return VForDict(VFor(data)) # type: ignore
@@ -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
@@ -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.skip import is_skip_output
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
- skips: Optional[List[int]] = None
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 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
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 = None if len(hash_part_with_extend_paths) == 1 else hash_part_with_extend_paths[1]
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.fn
25
- ), "handler must be a coroutine function"
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.response_data(handler.outputs_binding_count, result)
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.response_data(handler.outputs_binding_count, result)
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 :ContextVar[Optional[Request]]= ContextVar("current_request", default=None)
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
-
@@ -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.response_data(handler_info.outputs_binding_count, result)
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.response_data(handler_info.outputs_binding_count, result)
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
@@ -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)])