solara-ui 1.53.0__py3-none-any.whl → 1.55.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
solara/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Build webapps using IPywidgets"""
2
2
 
3
- __version__ = "1.53.0"
3
+ __version__ = "1.55.0"
4
4
  github_url = "https://github.com/widgetti/solara"
5
5
  git_branch = "master"
6
6
 
solara/minisettings.py CHANGED
@@ -101,37 +101,33 @@ class BaseSettings:
101
101
  cls = type(self)
102
102
  self._values = {**kwargs}
103
103
  keys = {k.upper() for k in os.environ.keys()}
104
- for key, field in cls.__dict__.items():
105
- if key in kwargs:
104
+
105
+ for key, field in list(cls.__dict__.items()):
106
+ if key in kwargs or not isinstance(field, _Field):
106
107
  continue
107
- if isinstance(field, _Field):
108
- value = field.default
109
- if field.default_factory:
110
- value = field.default_factory()
111
-
112
- if field.env:
113
- env_key = field.env.upper()
114
- if env_key in keys:
115
- # do a case-insensitive lookup
116
- for env_var_cased in os.environ.keys():
117
- if env_key.upper() == env_var_cased.upper():
118
- value = convert(field.annotation, os.environ[env_var_cased])
119
- self._values[key] = value
108
+ value = field.default
109
+ if field.default_factory:
110
+ value = field.default_factory()
111
+ if field.env:
112
+ env_key = field.env.upper()
113
+ if env_key in keys:
114
+ for env_var_cased in os.environ.keys():
115
+ if env_key.upper() == env_var_cased.upper():
116
+ value = convert(field.annotation, os.environ[env_var_cased])
117
+ self._values[key] = value
120
118
 
121
119
  def __init_subclass__(cls) -> None:
122
120
  cls.__fields__ = {}
123
- for key, field in cls.__dict__.items():
124
- if key.startswith("_"):
125
- continue
126
- if key == "Config":
121
+ items = list(cls.__dict__.items())
122
+
123
+ for key, value in items:
124
+ if key.startswith("_") or key == "Config" or inspect.isfunction(value):
127
125
  continue
128
- if not isinstance(field, _Field):
129
- if inspect.isfunction(field):
130
- continue
131
- field = Field(field)
132
- setattr(cls, key, field)
133
- field.__set_name__(cls, key)
134
- cls.__fields__[key] = field
126
+ if not isinstance(value, _Field):
127
+ value = Field(value)
128
+ setattr(cls, key, value)
129
+ value.__set_name__(cls, key)
130
+ cls.__fields__[key] = value
135
131
 
136
132
  def dict(self, by_alias=True):
137
133
  values = self._values.copy()
@@ -17,7 +17,7 @@ import threading
17
17
  import time
18
18
  import typing
19
19
  from pathlib import Path
20
- from typing import Any, Callable, Dict, List, Optional, cast
20
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Union, cast
21
21
 
22
22
  import ipywidgets as widgets
23
23
  import reacton
@@ -39,6 +39,11 @@ class Local(threading.local):
39
39
 
40
40
 
41
41
  local = Local()
42
+ # same idea, but for `async with ...`
43
+ if typing.TYPE_CHECKING:
44
+ async_stack = contextvars.ContextVar[Union[Tuple[Union[None, "VirtualKernelContext"], ...], None]](name="async_stack", default=None)
45
+ else:
46
+ async_stack = contextvars.ContextVar("async_stack", default=None)
42
47
 
43
48
 
44
49
  class PageStatus(enum.Enum):
@@ -100,6 +105,23 @@ class VirtualKernelContext:
100
105
  def on_close(self, f: Callable[[], None]):
101
106
  self._on_close_callbacks.append(f)
102
107
 
108
+ async def __aenter__(self):
109
+ stack = async_stack.get()
110
+ if stack is None:
111
+ stack = ()
112
+ key = get_current_thread_key()
113
+ async_stack.set(stack + (current_context.get(key, None),))
114
+ new_key = get_current_thread_key()
115
+ current_context[new_key] = self
116
+
117
+ async def __aexit__(self, *args):
118
+ key = get_current_thread_key()
119
+ assert local.kernel_context_stack is not None
120
+ stack = async_stack.get()
121
+ assert stack is not None
122
+ current_context[key] = stack[-1]
123
+ async_stack.set(stack[:-1])
124
+
103
125
  def __enter__(self):
104
126
  if local.kernel_context_stack is None:
105
127
  local.kernel_context_stack = []
@@ -364,6 +386,7 @@ else:
364
386
 
365
387
 
366
388
  def get_current_thread_key() -> str:
389
+ # consider renaming this to get_current_context_key
367
390
  if not solara.server.settings.kernel.threaded:
368
391
  if async_context_id is not None:
369
392
  try:
@@ -375,6 +398,13 @@ def get_current_thread_key() -> str:
375
398
  else:
376
399
  thread = threading.current_thread()
377
400
  key = get_thread_key(thread)
401
+ # this signals we are using `async with context`, which means we are interested in task-local context
402
+ stack = async_stack.get()
403
+ if stack is not None and len(stack) > 0:
404
+ current_task = asyncio.current_task()
405
+ if current_task is not None:
406
+ task_key = current_task.get_name()
407
+ key = f"{key}-task:{task_key}"
378
408
  return key
379
409
 
380
410
 
@@ -119,7 +119,7 @@ async def main():
119
119
  ]
120
120
  for dep in requirements:
121
121
  await micropip.install(dep, keep_going=True)
122
- await micropip.install("/wheels/solara-1.53.0-py2.py3-none-any.whl", keep_going=True)
122
+ await micropip.install("/wheels/solara-1.55.0-py2.py3-none-any.whl", keep_going=True)
123
123
  import solara
124
124
 
125
125
  el = solara.Warning("lala")
solara/settings.py CHANGED
@@ -57,6 +57,7 @@ class MainSettings(BaseSettings):
57
57
  allow_reactive_boolean: bool = True
58
58
  # TODO: also change default_container in solara/components/__init__.py
59
59
  default_container: Optional[str] = "Column"
60
+ allow_global_context: bool = True
60
61
 
61
62
  class Config:
62
63
  env_prefix = "solara_"
solara/tasks.py CHANGED
@@ -1,3 +1,4 @@
1
+ import contextvars
1
2
  import sys
2
3
  import abc
3
4
  import asyncio
@@ -267,7 +268,16 @@ class TaskAsyncio(Task[P, R]):
267
268
 
268
269
  if self.run_in_thread:
269
270
  thread_event_loop = asyncio.new_event_loop()
270
- self.current_task = current_task = thread_event_loop.create_task(self._async_run(call_event_loop, future, args, kwargs))
271
+
272
+ def create_task():
273
+ # remove the stack, since this thread starts with a fresh stack
274
+ import solara.server.kernel_context
275
+
276
+ solara.server.kernel_context.async_stack.set(None)
277
+ return thread_event_loop.create_task(self._async_run(call_event_loop, future, args, kwargs))
278
+
279
+ new_context = contextvars.copy_context()
280
+ self.current_task = current_task = new_context.run(create_task)
271
281
 
272
282
  def runs_in_thread():
273
283
  try:
@@ -298,7 +308,7 @@ class TaskAsyncio(Task[P, R]):
298
308
  raise
299
309
 
300
310
  self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.STARTING)
301
- thread = threading.Thread(target=runs_in_thread, daemon=True)
311
+ thread = threading.Thread(target=runs_in_thread, daemon=True, name=f"TaskAsyncio-{self.function.__name__}")
302
312
  thread.start()
303
313
  else:
304
314
  self.current_task = current_task = asyncio.create_task(self._async_run(call_event_loop, future, args, kwargs))
@@ -322,7 +332,6 @@ class TaskAsyncio(Task[P, R]):
322
332
 
323
333
  task_for_this_call = _get_current_task()
324
334
  assert task_for_this_call is not None
325
-
326
335
  if self.is_current():
327
336
  self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.STARTING)
328
337
 
@@ -35,7 +35,12 @@ if typing.TYPE_CHECKING:
35
35
 
36
36
  logger = logging.getLogger("solara.pytest_plugin")
37
37
 
38
- TEST_PORT_START = int(os.environ.get("PORT", "18765")) + 100 # do not interfere with the solara integration tests
38
+
39
+ # support for pytest-xdist
40
+ worker = os.environ.get("PYTEST_XDIST_WORKER", "gw0")
41
+ # +100 so we do not interfere with the solara integration tests, +1 worker id to avoid port conflicts with other workers
42
+ TEST_PORT_START = int(os.environ.get("PORT", "18765")) + int(worker[2:]) + 100
43
+
39
44
  TEST_HOST = solara.server.settings.main.host
40
45
  TIMEOUT = float(os.environ.get("SOLARA_PW_TIMEOUT", "18"))
41
46
  PYTEST_IPYWIDGETS_SOLARA_APP_WAIT_TIMEOUT = int(os.environ.get("PYTEST_IPYWIDGETS_SOLARA_APP_WAIT_TIMEOUT", "10"))
solara/toestand.py CHANGED
@@ -313,6 +313,10 @@ class KernelStore(ValueBase[S], ABC):
313
313
 
314
314
  def set(self, value: S):
315
315
  scope_dict, scope_id = self._get_dict()
316
+ if not solara.settings.main.allow_global_context and scope_id == "global":
317
+ raise RuntimeError(
318
+ f"No kernel context found, and global context is not allowed for task, context key was {solara.server.kernel_context.get_current_thread_key()}"
319
+ )
316
320
  old = self.get()
317
321
  if self.equals(old, value):
318
322
  return
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: solara-ui
3
- Version: 1.53.0
3
+ Version: 1.55.0
4
4
  Dynamic: Summary
5
5
  Project-URL: Home, https://www.github.com/widgetti/solara
6
6
  Project-URL: Documentation, https://solara.dev
@@ -1,6 +1,6 @@
1
1
  prefix/etc/jupyter/jupyter_notebook_config.d/solara.json,sha256=3UhTBQi6z7F7pPjmqXxfddv79c8VGR9H7zStDLp6AwY,115
2
2
  prefix/etc/jupyter/jupyter_server_config.d/solara.json,sha256=D9J-rYxAzyD5GOqWvuPjacGUVFHsYtTfZ4FUbRzRvIA,113
3
- solara/__init__.py,sha256=PW82ZYvLg6MTedeafdqhKEUYnVYaBHzpcqIJTlt_WwU,3647
3
+ solara/__init__.py,sha256=EOkfX79MWzQHzuZpwOLafmVnS4mN76-eJGAk_ztvw18,3647
4
4
  solara/__main__.py,sha256=J_f3D_mgiNicU_ay_I-qn08GOViSXm04Ym0mopqh2Os,24853
5
5
  solara/_stores.py,sha256=N2Ec-61XNFXwigBx8f5QYPx7gDXenCOBdmLPXiJB45E,12320
6
6
  solara/alias.py,sha256=9vfLzud77NP8in3OID9b5mmIO8NyrnFjN2_aE0lSb1k,216
@@ -15,13 +15,13 @@ solara/express.py,sha256=R0E2ewwL0m09KdoDNhF_ZF5TnC7ytty5rzM8hDykpGk,6919
15
15
  solara/kitchensink.py,sha256=RUx3kW6CQAz9PMxB1sPI03IH5xJfsaaXq3w9bBuC6rg,249
16
16
  solara/layout.py,sha256=YSsORvn-76LrVSmElS39CBnetyUL9f4hLG_a_SdH_QM,1782
17
17
  solara/lifecycle.py,sha256=acUtft_KHj0ZOv2l-X3VcQdma1Tme70jkUp6li8mbH0,1404
18
- solara/minisettings.py,sha256=Ys0GdWo8i44HU8ILjesZ4PdTXlzkHQdlKxTO42ng8EA,4852
18
+ solara/minisettings.py,sha256=dxhFZxYg-jBhyXzro8tUVjZNpb5Z0Kq_RpYBrn8eCEQ,4699
19
19
  solara/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  solara/reactive.py,sha256=KN0PJl-ivxjgQj008zyPGnORo5bTNaY77uASsSW0mFQ,3430
21
21
  solara/routing.py,sha256=G_iZKozdVoUuD-qSMyuPV6jeN4qBqujAUvekw036f88,9143
22
- solara/settings.py,sha256=FED5SYfw1W88U8SPk9iXSgSSvMHgPkU1auONSdk5cNs,2688
23
- solara/tasks.py,sha256=uaauVf5zuVROWlU5KD_uoBEjy81YcpUtJhxcxX_Rs88,37197
24
- solara/toestand.py,sha256=NLX0s3D76spZQLQAsCRPZNlGi4DixIeVJTVpB-XKFGA,34068
22
+ solara/settings.py,sha256=VeYkEdWeJePXldFvfdJteiiydWTqsNtpgsYscKm7lrA,2726
23
+ solara/tasks.py,sha256=Wjb-0ExfW2lMbT40sYG3yEzUMLOHKKzHshCahBOELk8,37599
24
+ solara/toestand.py,sha256=2fNxOFAg120yud4EmU2s8-aUx4N1D4T8VvdO6Oqi_4s,34357
25
25
  solara/util.py,sha256=UUO3BfhXb3tGP-uj8UuTYMx6kuph6PiDp4XXm-f6uyg,9697
26
26
  solara/validate_hooks.py,sha256=F0CYDOVF_23O1apJBIk9lZMq11JmkoE3BrVVT8QvZWI,9999
27
27
  solara/components/__init__.py,sha256=j5Tv0tyzs80Bsl5hvTIF_x-RQyAvr3ooqIXnABamW44,3214
@@ -113,7 +113,7 @@ solara/server/fastapi.py,sha256=qVIHn0_Kxr6zWqcBWySu5nnJ6pNTSDqb4EHIh-cqH_8,93
113
113
  solara/server/flask.py,sha256=sjhtTMiAUJ7oacZeL0m3Qem6E6B2knJ0ly845Ayyjio,9645
114
114
  solara/server/jupytertools.py,sha256=cYFIUjLX7n0uuEXqWVWvmV6sV7R_MNg8ZZlabQgw8vk,1320
115
115
  solara/server/kernel.py,sha256=ZIcVI-wYhjDo1o05PBjkd-Am-5wZ8lAlf7luhz5-_L8,13449
116
- solara/server/kernel_context.py,sha256=C6ZIf7NTzDaQ2Z1tpDt0zcV0ZXsis2nUTKUQJ3F9Fh4,19168
116
+ solara/server/kernel_context.py,sha256=nOgBgchZZmZpHpHtI6_JRpxgJApZ50eKVIOk_EnyXJo,20477
117
117
  solara/server/patch.py,sha256=KMoO3IK5sk-BWnwPNXhmGW4MGAgxGosgtUKMFf7oNm0,19902
118
118
  solara/server/qt.py,sha256=QdMxX2T0Ol_j3QHYwInDyT5Gy4sOhYljMPYfru5kwLg,3774
119
119
  solara/server/reload.py,sha256=BBH7QhrV1-e9RVyNE3uz1oPj1DagC3t_XSqGPNz0nJE,9747
@@ -146,7 +146,7 @@ solara/server/static/highlight-dark.css,sha256=xO8-vta9vG4s1OfJNHXWqiLWzx_gM03jo
146
146
  solara/server/static/highlight.css,sha256=k8ZdT5iwrGQ5tXTQHAXuxvZrSUq3kwCdEpy3mlFoZjs,2637
147
147
  solara/server/static/main-vuetify.js,sha256=R3qM4xMlstMpRUdRaul78p34z_Av2ONSTXksg2V9TqQ,9503
148
148
  solara/server/static/main.js,sha256=mcx4JNQ4Lg4pNdUIqMoZos1mZyYFS48yd_JNFFJUqIE,5679
149
- solara/server/static/solara_bootstrap.py,sha256=nX3-METI4RU45-HMQwg-mEDxN5CKLTBl0QwILUcnbNU,3195
149
+ solara/server/static/solara_bootstrap.py,sha256=RKkrTWa1-WGsLTkfcLZIZqI1mXHvxRBjlKiF-s4VjXM,3195
150
150
  solara/server/static/sun.svg,sha256=jEKBAGCr7b9zNYv0VUb7lMWKjnU2dX69_Ye_DZWGXJI,6855
151
151
  solara/server/static/webworker.js,sha256=cjAFz7-SygStHJnYlJUlJs-gE_7YQeQ-WBDcmKYyjvo,1372
152
152
  solara/server/templates/index.html.j2,sha256=JXQo1M-STFHLBOFetgG7509cAq8xUP0VAEtYDzz35fY,31
@@ -179,7 +179,7 @@ solara/template/portal/solara_portal/pages/article/__init__.py,sha256=6PgHyyeK1_
179
179
  solara/template/portal/solara_portal/pages/viz/__init__.py,sha256=l65uqBpFJ6Uh5XytXhLMnR-8G4FBnjqJg86OJX7_LO0,2858
180
180
  solara/template/portal/solara_portal/pages/viz/overview.py,sha256=GPlzFxUR0LRQhR96a_CVWquGTjHhDehL1PR03Tcm3gs,365
181
181
  solara/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
182
- solara/test/pytest_plugin.py,sha256=17waJlD9pHgTWHkCWN-WRzm7ejOLiUtsJui_LE4fX20,30126
182
+ solara/test/pytest_plugin.py,sha256=M7_LQg4vf7ZHi-0HX8dRwbIEOehK-tx9Yz5v7qZLdkQ,30294
183
183
  solara/website/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
184
184
  solara/website/utils.py,sha256=TfoExocZ8ko2hTcA7XDKI5FfKZ4gi3JTc_f9Oi5L7Fs,855
185
185
  solara/website/assets/custom.css,sha256=Qw_FZpAmPrus38TgYKmLmSgEYf6djWzO5pm2fF8kutk,9330
@@ -456,9 +456,9 @@ solara/widgets/vue/gridlayout.vue,sha256=LZk-YlqM7nv_7Y5TTq2xqfH1j2SLP1QOH5eiz7G
456
456
  solara/widgets/vue/html.vue,sha256=48K5rjp0AdJDeRV6F3nOHW3J0WXPeHn55r5pGClK2fU,112
457
457
  solara/widgets/vue/navigator.vue,sha256=3hhh2_sXpnsdM1vrs2nQ0bZHLCB10HhtQeYLS-tO85s,4790
458
458
  solara/widgets/vue/vegalite.vue,sha256=zhocRsUCNIRQCEbD16er5sYnuHU0YThatRHnorA3P18,4596
459
- solara_ui-1.53.0.data/data/etc/jupyter/jupyter_notebook_config.d/solara.json,sha256=3UhTBQi6z7F7pPjmqXxfddv79c8VGR9H7zStDLp6AwY,115
460
- solara_ui-1.53.0.data/data/etc/jupyter/jupyter_server_config.d/solara.json,sha256=D9J-rYxAzyD5GOqWvuPjacGUVFHsYtTfZ4FUbRzRvIA,113
461
- solara_ui-1.53.0.dist-info/METADATA,sha256=Ox2JiebnVW2eHcauEF_ps5NMBA0dap4OJGT--IlwoUs,7459
462
- solara_ui-1.53.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
463
- solara_ui-1.53.0.dist-info/licenses/LICENSE,sha256=fFJUz-CWzZ9nEc4QZKu44jMEoDr5fEW-SiqljKpD82E,1086
464
- solara_ui-1.53.0.dist-info/RECORD,,
459
+ solara_ui-1.55.0.data/data/etc/jupyter/jupyter_notebook_config.d/solara.json,sha256=3UhTBQi6z7F7pPjmqXxfddv79c8VGR9H7zStDLp6AwY,115
460
+ solara_ui-1.55.0.data/data/etc/jupyter/jupyter_server_config.d/solara.json,sha256=D9J-rYxAzyD5GOqWvuPjacGUVFHsYtTfZ4FUbRzRvIA,113
461
+ solara_ui-1.55.0.dist-info/METADATA,sha256=TI1NAM29BE5gmF1oBfwm_775SYrULb4JRuif0B7huWU,7459
462
+ solara_ui-1.55.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
463
+ solara_ui-1.55.0.dist-info/licenses/LICENSE,sha256=fFJUz-CWzZ9nEc4QZKu44jMEoDr5fEW-SiqljKpD82E,1086
464
+ solara_ui-1.55.0.dist-info/RECORD,,