flet 0.70.0.dev5776__py3-none-any.whl → 0.70.0.dev6145__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.

Potentially problematic release.


This version of flet might be problematic. Click here for more details.

Files changed (46) hide show
  1. flet/__init__.py +32 -4
  2. flet/components/__init__.py +0 -0
  3. flet/components/component.py +346 -0
  4. flet/components/component_decorator.py +24 -0
  5. flet/components/component_owned.py +22 -0
  6. flet/components/hooks/__init__.py +0 -0
  7. flet/components/hooks/hook.py +12 -0
  8. flet/components/hooks/use_callback.py +28 -0
  9. flet/components/hooks/use_context.py +91 -0
  10. flet/components/hooks/use_effect.py +104 -0
  11. flet/components/hooks/use_memo.py +52 -0
  12. flet/components/hooks/use_state.py +58 -0
  13. flet/components/memo.py +34 -0
  14. flet/components/observable.py +269 -0
  15. flet/components/public_utils.py +10 -0
  16. flet/components/utils.py +85 -0
  17. flet/controls/base_control.py +34 -10
  18. flet/controls/base_page.py +44 -40
  19. flet/controls/context.py +22 -1
  20. flet/controls/control_event.py +19 -2
  21. flet/controls/core/column.py +5 -0
  22. flet/controls/core/drag_target.py +17 -8
  23. flet/controls/core/row.py +5 -0
  24. flet/controls/core/view.py +6 -6
  25. flet/controls/cupertino/cupertino_icons.py +1 -1
  26. flet/controls/id_counter.py +24 -0
  27. flet/controls/material/divider.py +6 -0
  28. flet/controls/material/icons.py +1 -1
  29. flet/controls/material/textfield.py +10 -1
  30. flet/controls/material/vertical_divider.py +6 -0
  31. flet/controls/object_patch.py +434 -197
  32. flet/controls/page.py +203 -84
  33. flet/controls/services/haptic_feedback.py +0 -3
  34. flet/controls/services/shake_detector.py +0 -3
  35. flet/messaging/flet_socket_server.py +13 -6
  36. flet/messaging/session.py +103 -10
  37. flet/{controls/session_storage.py → messaging/session_store.py} +2 -2
  38. flet/version.py +1 -1
  39. {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev6145.dist-info}/METADATA +4 -5
  40. {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev6145.dist-info}/RECORD +43 -30
  41. flet/controls/cache.py +0 -87
  42. flet/controls/control_id.py +0 -22
  43. flet/controls/core/state_view.py +0 -60
  44. {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev6145.dist-info}/WHEEL +0 -0
  45. {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev6145.dist-info}/entry_points.txt +0 -0
  46. {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev6145.dist-info}/top_level.txt +0 -0
flet/__init__.py CHANGED
@@ -1,4 +1,19 @@
1
1
  from flet.app import app, app_async, run, run_async
2
+ from flet.components.component import Component
3
+ from flet.components.component_decorator import component
4
+ from flet.components.hooks.use_callback import use_callback
5
+ from flet.components.hooks.use_context import create_context, use_context
6
+ from flet.components.hooks.use_effect import (
7
+ on_mounted,
8
+ on_unmounted,
9
+ on_updated,
10
+ use_effect,
11
+ )
12
+ from flet.components.hooks.use_memo import use_memo
13
+ from flet.components.hooks.use_state import use_state
14
+ from flet.components.memo import memo
15
+ from flet.components.observable import Observable, observable
16
+ from flet.components.public_utils import unwrap_component
2
17
  from flet.controls import alignment, border, border_radius, margin, padding
3
18
  from flet.controls.adaptive_control import AdaptiveControl
4
19
  from flet.controls.alignment import Alignment, Axis
@@ -48,7 +63,6 @@ from flet.controls.buttons import (
48
63
  ShapeBorder,
49
64
  StadiumBorder,
50
65
  )
51
- from flet.controls.cache import cache
52
66
  from flet.controls.colors import Colors
53
67
  from flet.controls.context import Context, context
54
68
  from flet.controls.control import Control
@@ -116,7 +130,6 @@ from flet.controls.core.screenshot import Screenshot
116
130
  from flet.controls.core.semantics import Semantics
117
131
  from flet.controls.core.shader_mask import ShaderMask
118
132
  from flet.controls.core.stack import Stack, StackFit
119
- from flet.controls.core.state_view import StateView
120
133
  from flet.controls.core.text import (
121
134
  Text,
122
135
  TextAffinity,
@@ -218,6 +231,7 @@ from flet.controls.gradients import (
218
231
  SweepGradient,
219
232
  )
220
233
  from flet.controls.icon_data import IconData
234
+ from flet.controls.id_counter import IdCounter
221
235
  from flet.controls.keys import Key, KeyValue, ScrollKey, ValueKey
222
236
  from flet.controls.layout_control import ConstrainedControl, LayoutControl
223
237
  from flet.controls.margin import Margin, MarginValue
@@ -574,6 +588,7 @@ __all__ = [
574
588
  "ColorValue",
575
589
  "Colors",
576
590
  "Column",
591
+ "Component",
577
592
  "ConstrainedControl",
578
593
  "Container",
579
594
  "Context",
@@ -691,6 +706,7 @@ __all__ = [
691
706
  "IconDataOrControl",
692
707
  "IconTheme",
693
708
  "Icons",
709
+ "IdCounter",
694
710
  "Image",
695
711
  "ImageRepeat",
696
712
  "InputBorder",
@@ -749,6 +765,7 @@ __all__ = [
749
765
  "NotchShape",
750
766
  "Number",
751
767
  "NumbersOnlyInputFilter",
768
+ "Observable",
752
769
  "Offset",
753
770
  "OffsetValue",
754
771
  "OnReorderEvent",
@@ -845,7 +862,6 @@ __all__ = [
845
862
  "Stack",
846
863
  "StackFit",
847
864
  "StadiumBorder",
848
- "StateView",
849
865
  "StoragePaths",
850
866
  "StrOrControl",
851
867
  "StrokeCap",
@@ -920,16 +936,28 @@ __all__ = [
920
936
  "app_async",
921
937
  "border",
922
938
  "border_radius",
923
- "cache",
939
+ "component",
924
940
  "context",
925
941
  "control",
942
+ "create_context",
926
943
  "cupertino_colors",
927
944
  "cupertino_icons",
928
945
  "dropdown",
929
946
  "dropdownm2",
930
947
  "icons",
931
948
  "margin",
949
+ "memo",
950
+ "observable",
951
+ "on_mounted",
952
+ "on_unmounted",
953
+ "on_updated",
932
954
  "padding",
933
955
  "run",
934
956
  "run_async",
957
+ "unwrap_component",
958
+ "use_callback",
959
+ "use_context",
960
+ "use_effect",
961
+ "use_memo",
962
+ "use_state",
935
963
  ]
File without changes
@@ -0,0 +1,346 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import weakref
5
+ from collections import defaultdict
6
+ from dataclasses import dataclass, field
7
+ from typing import Any, Callable, TypeVar
8
+
9
+ from flet.components.hooks.hook import Hook
10
+ from flet.components.hooks.use_effect import EffectHook
11
+ from flet.components.observable import Observable, ObservableSubscription
12
+ from flet.components.utils import (
13
+ _CURRENT_RENDERER,
14
+ shallow_compare_args_and_kwargs,
15
+ )
16
+ from flet.controls.base_control import BaseControl, control
17
+ from flet.controls.context import context
18
+
19
+ logger = logging.getLogger("flet_components")
20
+ logger.setLevel(logging.INFO)
21
+
22
+
23
+ @dataclass
24
+ class _ComponentState:
25
+ hooks: list[Hook] = field(default_factory=list)
26
+ hook_cursor: int = 0
27
+ mounted: bool = False
28
+ is_dirty: bool = False
29
+ observable_subscriptions: list[ObservableSubscription] = field(default_factory=list)
30
+ last_args: tuple[Any, ...] = field(default_factory=tuple)
31
+ last_kwargs: dict[str, Any] = field(default_factory=dict)
32
+ last_b: Any = None
33
+
34
+ def change_owner(self, new_owner: Any):
35
+ for hook in self.hooks:
36
+ hook.component = new_owner
37
+ for sub in self.observable_subscriptions:
38
+ sub.component = new_owner
39
+
40
+
41
+ HookTypeT = TypeVar("HookTypeT", bound=Hook)
42
+
43
+
44
+ @control("C")
45
+ class Component(BaseControl):
46
+ fn: Callable[..., Any] = field(metadata={"skip": True})
47
+ args: tuple[Any, ...] = field(default_factory=tuple, metadata={"skip": True})
48
+ kwargs: dict[str, Any] = field(default_factory=dict, metadata={"skip": True})
49
+ _parent_component: weakref.ref[Component] | None = field(
50
+ default=None, metadata={"skip": True}
51
+ )
52
+ _state: _ComponentState = field(
53
+ default_factory=_ComponentState, metadata={"skip": True}
54
+ )
55
+ _contexts: dict[object, Any] = field(default_factory=dict, metadata={"skip": True})
56
+ memoized: bool = field(default=False, metadata={"skip": True})
57
+ _stale: bool = field(default=False, metadata={"skip": True})
58
+
59
+ _b: Any = None # body
60
+
61
+ def _migrate_state(self, other: BaseControl):
62
+ super()._migrate_state(other)
63
+ logger.debug("%s._migrate_state(%s)", self, other)
64
+ if not isinstance(other, Component):
65
+ return
66
+ self._state = other._state
67
+ self._state.change_owner(self)
68
+ other._stale = True
69
+
70
+ def update(self):
71
+ if self._stale:
72
+ logger.debug("%s.update(): skipping (stale)", self)
73
+ return
74
+
75
+ logger.debug(
76
+ "%s.update(), memoized: %s",
77
+ self,
78
+ self.memoized,
79
+ )
80
+
81
+ self._state.is_dirty = False
82
+
83
+ # new rendering
84
+ self._state.hook_cursor = 0
85
+ self._detach_observable_subscriptions()
86
+ self._subscribe_observable_args(self.args, self.kwargs)
87
+
88
+ b = Renderer(self).render(self.fn, *self.args, **self.kwargs)
89
+
90
+ for item in b if isinstance(b, list) else [b] if b is not None else []:
91
+ object.__setattr__(item, "_frozen", True)
92
+
93
+ if self.memoized and b is not None:
94
+ logger.debug("%s.update(): memoizing", self)
95
+ self._state.last_b = b
96
+ self._state.last_args = self.args
97
+ self._state.last_kwargs = self.kwargs
98
+
99
+ # patch component
100
+ if b is not None:
101
+ context.page.session.patch_control(
102
+ prev_control={"_b": self._b},
103
+ control={"_b": b},
104
+ parent=self,
105
+ path=[],
106
+ frozen=True,
107
+ )
108
+
109
+ self._b = b
110
+ self._run_render_effects()
111
+
112
+ def before_update(self):
113
+ logger.debug("%s.before_update(), memoized: %s", self, self.memoized)
114
+ is_dirty = self._state.is_dirty
115
+ self._state.is_dirty = False
116
+
117
+ if (
118
+ self.memoized
119
+ and not is_dirty
120
+ and shallow_compare_args_and_kwargs(
121
+ self._state.last_args, self._state.last_kwargs, self.args, self.kwargs
122
+ )
123
+ and self._state.last_b is not None
124
+ ):
125
+ logger.debug("%s.before_update(): skipping (memo)", self)
126
+ self._b = self._state.last_b
127
+
128
+ # fix parent
129
+ for item in self._b if isinstance(self._b, list) else [self._b]:
130
+ object.__setattr__(item, "_parent", weakref.ref(self))
131
+ return
132
+
133
+ self._state.hook_cursor = 0
134
+ self._detach_observable_subscriptions()
135
+ self._subscribe_observable_args(self.args, self.kwargs)
136
+ b = Renderer(self).render(self.fn, *self.args, **self.kwargs)
137
+
138
+ for item in b if isinstance(b, list) else [b] if b is not None else []:
139
+ object.__setattr__(item, "_frozen", True)
140
+
141
+ if self.memoized and b is not None:
142
+ logger.debug("%s.before_update(): memoizing", self)
143
+ self._state.last_b = b
144
+ self._state.last_args = self.args
145
+ self._state.last_kwargs = self.kwargs
146
+ self._b = b
147
+ self._run_render_effects()
148
+
149
+ def _schedule_update(self):
150
+ logger.debug("%s.schedule_update()", self)
151
+ self._state.is_dirty = True
152
+ context.page.session.schedule_update(self)
153
+
154
+ def _schedule_effect(self, hook: EffectHook, is_cleanup: bool = False):
155
+ logger.debug("%s.schedule_effect(%s, %s)", self, hook, is_cleanup)
156
+ context.page.session.schedule_effect(hook, is_cleanup)
157
+
158
+ def _subscribe_observable_args(self, args: tuple[Any, ...], kwargs: dict[str, Any]):
159
+ for a in args:
160
+ if isinstance(a, Observable):
161
+ self._attach_observable_subscription(a)
162
+ for v in kwargs.values():
163
+ if isinstance(v, Observable):
164
+ self._attach_observable_subscription(v)
165
+
166
+ def _attach_observable_subscription(self, observable: Observable):
167
+ # Use weak refs to avoid cycles
168
+ logger.debug("%s._attach_observable_subscription(%s)", self, observable)
169
+
170
+ self._state.observable_subscriptions.append(
171
+ ObservableSubscription(owner=self, observable=observable)
172
+ )
173
+ return self._state.observable_subscriptions[-1]
174
+
175
+ def _detach_observable_subscription(self, subscription: ObservableSubscription):
176
+ if subscription in self._state.observable_subscriptions:
177
+ subscription.dispose()
178
+ self._state.observable_subscriptions.remove(subscription)
179
+
180
+ def _detach_observable_subscriptions(self):
181
+ for subscription in self._state.observable_subscriptions:
182
+ subscription.dispose()
183
+ self._state.observable_subscriptions.clear()
184
+
185
+ def use_hook(self, default: Callable[[], HookTypeT]) -> HookTypeT:
186
+ hook_cursor = self._state.hook_cursor
187
+
188
+ i = hook_cursor
189
+ hook_cursor += 1
190
+
191
+ if i >= len(self._state.hooks):
192
+ self._state.hooks.append(default())
193
+
194
+ self._state.hook_cursor = hook_cursor
195
+ return self._state.hooks[i] # type: ignore
196
+
197
+ def _run_mount_effects(self):
198
+ if self._state.hooks:
199
+ logger.debug("%s._run_mount_effects()", self)
200
+ for hook in self._state.hooks:
201
+ if isinstance(hook, EffectHook):
202
+ # all effects are running on mount
203
+ self._schedule_effect(hook, is_cleanup=False)
204
+
205
+ def _run_render_effects(self):
206
+ if not self._state.mounted:
207
+ return
208
+ if self._state.hooks:
209
+ logger.debug("%s._run_render_effects()", self)
210
+ for hook in self._state.hooks:
211
+ if isinstance(hook, EffectHook) and hook.deps != []:
212
+ if callable(hook.cleanup):
213
+ self._schedule_effect(hook, is_cleanup=False)
214
+ if (
215
+ hook.deps is None
216
+ or hook.prev_deps is None
217
+ or hook.deps != hook.prev_deps
218
+ ):
219
+ self._schedule_effect(hook, is_cleanup=False)
220
+
221
+ def _run_unmount_effects(self):
222
+ if self._state.hooks:
223
+ logger.debug("%s._run_unmount_effects()", self)
224
+ for hook in self._state.hooks:
225
+ # all effects are running on unmount
226
+ if isinstance(hook, EffectHook) and callable(hook.cleanup):
227
+ self._schedule_effect(hook, is_cleanup=True)
228
+
229
+ def did_mount(self):
230
+ super().did_mount()
231
+ self._state.mounted = True
232
+ self._run_mount_effects()
233
+
234
+ def will_unmount(self):
235
+ super().will_unmount()
236
+ self._state.mounted = False
237
+ self._detach_observable_subscriptions()
238
+ self._run_unmount_effects()
239
+
240
+ def __str__(self):
241
+ return f"{self._c}:{self.fn.__name__}({self._i} - {id(self)})"
242
+
243
+
244
+ #
245
+ # Renderer
246
+ #
247
+
248
+
249
+ class Renderer:
250
+ _ROOT_TOKEN = ("__root__",)
251
+
252
+ def __init__(self, root_component=None):
253
+ self._root_component = root_component
254
+ self._render_stack: list[Component] = []
255
+ self._is_memo = False
256
+ self._contexts: dict[object, list[object]] = defaultdict(list)
257
+
258
+ def set_memo(self):
259
+ self._is_memo = True
260
+
261
+ def push_context(self, key: object, value: object) -> None:
262
+ logger.debug("Renderer._push_context(%s, %s)", key, value)
263
+ self._contexts[key].append(value)
264
+
265
+ def pop_context(self, key: object) -> None:
266
+ logger.debug("Renderer._pop_context(%s)", key)
267
+ stack = self._contexts.get(key)
268
+ if stack:
269
+ stack.pop()
270
+ if not stack:
271
+ del self._contexts[key]
272
+
273
+ def _snapshot_contexts(self) -> dict[object, object]:
274
+ # take top of each stack
275
+ return {k: v[-1] for k, v in self._contexts.items() if v}
276
+
277
+ def with_context(self):
278
+ """Context manager to make this renderer the 'current' one."""
279
+
280
+ class _C:
281
+ def __init__(_s, r: Renderer):
282
+ _s._tok = None
283
+ _s._r = r
284
+
285
+ def __enter__(_s):
286
+ _s._tok = _CURRENT_RENDERER.set(_s._r)
287
+ return _s._r
288
+
289
+ def __exit__(_s, exc_type, exc, tb):
290
+ if _s._tok is not None:
291
+ _CURRENT_RENDERER.reset(_s._tok)
292
+
293
+ return _C(self)
294
+
295
+ def render(self, root_fn: Callable[..., Any], *args, **kwargs):
296
+ # run with this renderer bound as current
297
+ with self.with_context(), self._Frame(self, self._root_component):
298
+ return root_fn(*args, **kwargs)
299
+
300
+ def render_component(
301
+ self,
302
+ fn: Callable[..., Any],
303
+ args: tuple[Any, ...],
304
+ kwargs: dict[str, Any],
305
+ key=None,
306
+ ):
307
+ logger.debug(
308
+ "Renderer._render_component(%s, %s, %s, %s)", fn, args, kwargs, key
309
+ )
310
+ parent_component = len(self._render_stack) and self._render_stack[-1]
311
+
312
+ if not hasattr(fn, "__is_component__"):
313
+ raise ValueError(f"Function {fn} is not a component (missing @component?)")
314
+
315
+ c = Component(
316
+ fn=fn,
317
+ args=args,
318
+ kwargs=kwargs,
319
+ _parent_component=weakref.ref(parent_component)
320
+ if parent_component
321
+ else None,
322
+ memoized=self._is_memo,
323
+ key=key,
324
+ )
325
+ c._contexts = self._snapshot_contexts()
326
+ c._frozen = True
327
+
328
+ self._is_memo = False
329
+
330
+ return c
331
+
332
+ class _Frame:
333
+ """Context around entering a component; pushes/pops on renderer's stack."""
334
+
335
+ def __init__(self, renderer: Renderer, c: Component | None = None):
336
+ self.r = renderer
337
+ self.c = c
338
+
339
+ def __enter__(self):
340
+ if self.c:
341
+ self.r._render_stack.append(self.c)
342
+ return self.c
343
+
344
+ def __exit__(self, exc_type, exc, tb):
345
+ if self.c:
346
+ self.r._render_stack.pop()
@@ -0,0 +1,24 @@
1
+ from functools import wraps
2
+ from typing import Callable, ParamSpec, TypeVar
3
+
4
+ from flet.components.utils import current_renderer
5
+
6
+ P = ParamSpec("P")
7
+ R = TypeVar("R")
8
+
9
+
10
+ def component(fn: Callable[P, R]) -> Callable[P, R]:
11
+ """
12
+ Marks a function as a component.
13
+ """
14
+ fn.__is_component__ = True
15
+
16
+ @wraps(fn)
17
+ def component_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
18
+ key = kwargs.pop("key", None)
19
+ r = current_renderer()
20
+ return r.render_component(fn, args, kwargs, key=key)
21
+
22
+ component_wrapper.__is_component__ = True
23
+ component_wrapper.__component_impl__ = fn
24
+ return component_wrapper
@@ -0,0 +1,22 @@
1
+ import weakref
2
+ from dataclasses import InitVar, dataclass
3
+ from typing import TYPE_CHECKING, Optional
4
+
5
+ if TYPE_CHECKING:
6
+ from flet.components.component import Component
7
+
8
+
9
+ @dataclass()
10
+ class ComponentOwned:
11
+ owner: InitVar["Component"]
12
+
13
+ def __post_init__(self, owner: "Component") -> None:
14
+ self._component = weakref.ref(owner)
15
+
16
+ @property
17
+ def component(self) -> Optional["Component"]:
18
+ return self._component()
19
+
20
+ @component.setter
21
+ def component(self, value: "Component") -> None:
22
+ self._component = weakref.ref(value)
File without changes
@@ -0,0 +1,12 @@
1
+ from dataclasses import dataclass
2
+ from typing import TYPE_CHECKING
3
+
4
+ from flet.components.component_owned import ComponentOwned
5
+
6
+ if TYPE_CHECKING:
7
+ pass
8
+
9
+
10
+ @dataclass()
11
+ class Hook(ComponentOwned):
12
+ pass
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Sequence
4
+ from typing import Any, Callable, ParamSpec, TypeVar
5
+
6
+ from flet.components.hooks.use_memo import use_memo
7
+
8
+ P = ParamSpec("P")
9
+ R = TypeVar("R")
10
+
11
+
12
+ def use_callback(
13
+ fn: Callable[P, R],
14
+ dependencies: Sequence[Any] | None = None,
15
+ ) -> Callable[P, R]:
16
+ """
17
+ Memoize a function identity between renders.
18
+
19
+ Args:
20
+ fn: A function to memoize.
21
+ dependencies: If present, fn is only re-memoized when one of the dependencies
22
+ has changed. If absent, fn is only memoized on initial render.
23
+
24
+ Returns:
25
+ A memoized version of the function whose identity is stable between renders.
26
+ """
27
+ # Just memoize the function object itself
28
+ return use_memo(lambda: fn, dependencies)
@@ -0,0 +1,91 @@
1
+ from collections.abc import Callable
2
+ from dataclasses import dataclass
3
+ from typing import TypeVar, cast
4
+
5
+ try:
6
+ from typing import Protocol
7
+ except ImportError:
8
+ from typing_extensions import Protocol
9
+
10
+ from flet.components.hooks.hook import Hook
11
+ from flet.components.observable import Observable
12
+ from flet.components.utils import current_component, current_renderer
13
+
14
+
15
+ @dataclass
16
+ class ContextHook(Hook):
17
+ pass
18
+
19
+
20
+ ContextValueT = TypeVar("ContextValueT")
21
+
22
+ T = TypeVar("T") # context value type
23
+ ProviderResultT = TypeVar(
24
+ "ProviderResultT", covariant=True
25
+ ) # return type of the callback/provider
26
+
27
+
28
+ class ContextProvider(Protocol[T]):
29
+ default_value: T
30
+ _key: object
31
+
32
+ # Generic call: whatever the callback returns (ProviderResultT),
33
+ # the provider returns too.
34
+ def __call__(
35
+ self, value: T, callback: Callable[[], ProviderResultT]
36
+ ) -> ProviderResultT: ...
37
+
38
+
39
+ def create_context(default_value: T) -> ContextProvider[T]:
40
+ """
41
+ Create a context provider.
42
+
43
+ Args:
44
+ default_value: Default context value when no provider is found.
45
+
46
+ Returns:
47
+ A context provider that can be used with `use_context` and to wrap
48
+ parts of the component tree that should share the same context value.
49
+ """
50
+ key = object()
51
+
52
+ def provider(value: T, callback: Callable[[], ProviderResultT]) -> ProviderResultT: # type: ignore[type-var]
53
+ r = current_renderer()
54
+ r.push_context(key, value)
55
+ try:
56
+ return callback() # type: ignore[return-value]
57
+ finally:
58
+ r.pop_context(key)
59
+
60
+ p = cast(ContextProvider[T], provider)
61
+ p.default_value = default_value
62
+ p._key = key
63
+ return p
64
+
65
+
66
+ def use_context(context: ContextProvider[T]) -> T:
67
+ """
68
+ Access the current context value.
69
+
70
+ Args:
71
+ context: A context provider created by `create_context`.
72
+
73
+ Returns:
74
+ The current context value, or the default value if no provider is found.
75
+ """
76
+ component = current_component()
77
+ component.use_hook(lambda: ContextHook(component))
78
+
79
+ value = cast(T, context.default_value)
80
+ # look up the component tree for the nearest context provider
81
+ comp = component
82
+ while comp:
83
+ if context._key in comp._contexts:
84
+ value = cast(T, comp._contexts[context._key])
85
+ break
86
+ comp = comp._parent_component() if comp._parent_component else None
87
+
88
+ if isinstance(value, Observable):
89
+ component._attach_observable_subscription(value)
90
+
91
+ return value