solara-ui 1.35.1__py2.py3-none-any.whl → 1.37.0__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.
- solara/__init__.py +2 -2
- solara/autorouting.py +12 -1
- solara/core.py +42 -0
- solara/server/server.py +3 -2
- solara/server/static/solara_bootstrap.py +1 -1
- solara/server/templates/solara.html.j2 +6 -0
- solara/settings.py +13 -0
- solara/tasks.py +2 -2
- solara/validate_hooks.py +260 -0
- solara/website/pages/changelog/changelog.md +15 -0
- solara/website/pages/documentation/advanced/content/20-understanding/17-rules-of-hooks.md +186 -1
- {solara_ui-1.35.1.dist-info → solara_ui-1.37.0.dist-info}/METADATA +1 -1
- {solara_ui-1.35.1.dist-info → solara_ui-1.37.0.dist-info}/RECORD +17 -15
- {solara_ui-1.35.1.data → solara_ui-1.37.0.data}/data/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
- {solara_ui-1.35.1.data → solara_ui-1.37.0.data}/data/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
- {solara_ui-1.35.1.dist-info → solara_ui-1.37.0.dist-info}/WHEEL +0 -0
- {solara_ui-1.35.1.dist-info → solara_ui-1.37.0.dist-info}/licenses/LICENSE +0 -0
solara/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Build webapps using IPywidgets"""
|
|
2
2
|
|
|
3
|
-
__version__ = "1.
|
|
3
|
+
__version__ = "1.37.0"
|
|
4
4
|
github_url = "https://github.com/widgetti/solara"
|
|
5
5
|
git_branch = "master"
|
|
6
6
|
|
|
@@ -21,7 +21,6 @@ def _using_solara_server():
|
|
|
21
21
|
# isort: skip_file
|
|
22
22
|
# ruff: noqa: F401 F403
|
|
23
23
|
from reacton import (
|
|
24
|
-
component,
|
|
25
24
|
component_interactive,
|
|
26
25
|
value_component,
|
|
27
26
|
create_context,
|
|
@@ -42,6 +41,7 @@ from reacton import (
|
|
|
42
41
|
use_state_widget,
|
|
43
42
|
)
|
|
44
43
|
from reacton.core import Element
|
|
44
|
+
from .core import component
|
|
45
45
|
|
|
46
46
|
try:
|
|
47
47
|
import ipyvuetify.components as v # type: ignore
|
solara/autorouting.py
CHANGED
|
@@ -4,6 +4,7 @@ import importlib
|
|
|
4
4
|
import inspect
|
|
5
5
|
import pkgutil
|
|
6
6
|
import re
|
|
7
|
+
import linecache
|
|
7
8
|
import warnings
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from types import ModuleType
|
|
@@ -46,7 +47,17 @@ def source_to_module(path: Path, initial_namespace={}) -> ModuleType:
|
|
|
46
47
|
cell_index += 1 # used 1 based
|
|
47
48
|
if cell.cell_type == "code":
|
|
48
49
|
source = cell.source
|
|
49
|
-
|
|
50
|
+
# similar to ipython/ipython/IPython/core/compilerop.py
|
|
51
|
+
# inspect.py:findsource accepts non-filenames with '<' and '>' around it
|
|
52
|
+
cell_path = f"<{str(path)} input cell {cell_index}>"
|
|
53
|
+
# put an entry in linecache so that we can use inspect.getsource
|
|
54
|
+
entry = (
|
|
55
|
+
len(source),
|
|
56
|
+
None,
|
|
57
|
+
[line + "\n" for line in source.splitlines()],
|
|
58
|
+
cell_path,
|
|
59
|
+
)
|
|
60
|
+
linecache.cache[cell_path] = entry
|
|
50
61
|
ast = compile(source, cell_path, "exec")
|
|
51
62
|
exec(ast, mod.__dict__)
|
|
52
63
|
else:
|
solara/core.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from typing import Any, Callable, Dict, Union, overload, TypeVar
|
|
3
|
+
import typing_extensions
|
|
4
|
+
import reacton
|
|
5
|
+
from . import validate_hooks
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
P = typing_extensions.ParamSpec("P")
|
|
9
|
+
FuncT = TypeVar("FuncT", bound=Callable[..., reacton.core.Element])
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@overload
|
|
13
|
+
def component(obj: None = None, mime_bundle: Dict[str, Any] = ...) -> Callable[[FuncT], FuncT]: ...
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@overload
|
|
17
|
+
def component(obj: FuncT, mime_bundle: Dict[str, Any] = ...) -> FuncT: ...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@overload
|
|
21
|
+
def component(obj: Callable[P, None], mime_bundle: Dict[str, Any] = ...) -> Callable[P, reacton.core.Element]: ...
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def component(
|
|
25
|
+
obj: Union[Callable[P, None], FuncT, None] = None, mime_bundle: Dict[str, Any] = reacton.core.mime_bundle_default
|
|
26
|
+
) -> Union[Callable[[FuncT], FuncT], FuncT, Callable[P, reacton.core.Element]]:
|
|
27
|
+
def wrapper(obj: Union[Callable[P, None], FuncT]) -> FuncT:
|
|
28
|
+
try:
|
|
29
|
+
validate_hooks.HookValidator(obj).run()
|
|
30
|
+
except Exception as e:
|
|
31
|
+
if not isinstance(e, validate_hooks.HookValidationError):
|
|
32
|
+
# we probably failed because of a unknown reason, but we do not want to break the user's code
|
|
33
|
+
warnings.warn(f"Failed to validate hooks for component {obj.__qualname__}: {e}")
|
|
34
|
+
else:
|
|
35
|
+
raise
|
|
36
|
+
|
|
37
|
+
return reacton.component(obj, mime_bundle) # type: ignore
|
|
38
|
+
|
|
39
|
+
if obj is not None:
|
|
40
|
+
return wrapper(obj)
|
|
41
|
+
else:
|
|
42
|
+
return wrapper
|
solara/server/server.py
CHANGED
|
@@ -452,8 +452,9 @@ def get_nbextensions() -> Tuple[List[str], Dict[str, Optional[str]]]:
|
|
|
452
452
|
file_path = directory / (name + ".js")
|
|
453
453
|
if file_path.exists():
|
|
454
454
|
for file in directory.glob("**/*.*"):
|
|
455
|
-
|
|
456
|
-
|
|
455
|
+
if file.is_file(): # Otherwise directories with a dot in the name are included
|
|
456
|
+
data = file.read_bytes()
|
|
457
|
+
h.update(data)
|
|
457
458
|
except PermissionError:
|
|
458
459
|
logger.warning(f"Caught PermissionError while checking for existence of nbextension {name!r} at path: {file_path}. This path will be ignored.")
|
|
459
460
|
|
|
@@ -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.
|
|
122
|
+
await micropip.install("/wheels/solara-1.37.0-py2.py3-none-any.whl", keep_going=True)
|
|
123
123
|
import solara
|
|
124
124
|
|
|
125
125
|
el = solara.Warning("lala")
|
|
@@ -211,6 +211,12 @@
|
|
|
211
211
|
}
|
|
212
212
|
// Init theme
|
|
213
213
|
let appContainer = document.getElementById('app');
|
|
214
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
215
|
+
if(searchParams.has('modelid')) {
|
|
216
|
+
// indicates that we are not mounting the application, but a specific
|
|
217
|
+
// widget (for instance using ipypopout)
|
|
218
|
+
document.body.classList.add('jupyter-widgets-popout-container');
|
|
219
|
+
}
|
|
214
220
|
if (inDarkMode()) {
|
|
215
221
|
appContainer.classList.remove('theme--light');
|
|
216
222
|
appContainer.classList.add('theme--dark');
|
solara/settings.py
CHANGED
|
@@ -52,5 +52,18 @@ class Assets(BaseSettings):
|
|
|
52
52
|
env_file = ".env"
|
|
53
53
|
|
|
54
54
|
|
|
55
|
+
class MainSettings(BaseSettings):
|
|
56
|
+
check_hooks: str = "warn"
|
|
57
|
+
|
|
58
|
+
class Config:
|
|
59
|
+
env_prefix = "solara_"
|
|
60
|
+
case_sensitive = False
|
|
61
|
+
env_file = ".env"
|
|
62
|
+
|
|
63
|
+
|
|
55
64
|
assets: Assets = Assets()
|
|
56
65
|
cache: Cache = Cache()
|
|
66
|
+
main = MainSettings()
|
|
67
|
+
|
|
68
|
+
if main.check_hooks not in ["off", "warn", "raise"]:
|
|
69
|
+
raise ValueError(f"Invalid value for check_hooks: {main.check_hooks}, expected one of ['off', 'warn', 'raise']")
|
solara/tasks.py
CHANGED
|
@@ -740,7 +740,7 @@ def use_task(
|
|
|
740
740
|
|
|
741
741
|
Unlike with the [`@task`](/api/task) decorator, the result is not globally shared, but only available to the component that called `use_task`.
|
|
742
742
|
|
|
743
|
-
Note that unlike the [`@task`](/api/task) decorator, the task is invoked immediately when dependencies are passed.
|
|
743
|
+
Note that unlike the [`@task`](/api/task) decorator, the task is invoked immediately when dependencies are passed. To prevent this, pass `dependencies=None`.
|
|
744
744
|
|
|
745
745
|
|
|
746
746
|
## Example
|
|
@@ -799,7 +799,7 @@ def use_task(
|
|
|
799
799
|
## Arguments
|
|
800
800
|
|
|
801
801
|
- `f`: The function or coroutine to run as a task.
|
|
802
|
-
- `dependencies`: A list of dependencies that will trigger a rerun of the task when changed
|
|
802
|
+
- `dependencies`: A list of dependencies that will trigger a rerun of the task when changed, the task will run automatically execute when the `dependencies=None`
|
|
803
803
|
- `raise_error`: If true, an error in the task will be raised. If false, the error should be handled by the
|
|
804
804
|
user and is available in the `.exception` attribute of the task result object.
|
|
805
805
|
- `prefer_threaded` - bool: Will run coroutine functions as a task in a thread when threads are available.
|
solara/validate_hooks.py
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import inspect
|
|
3
|
+
import re
|
|
4
|
+
import typing as t
|
|
5
|
+
from enum import Enum
|
|
6
|
+
import sys
|
|
7
|
+
import warnings
|
|
8
|
+
|
|
9
|
+
DEFAULT_USE_FUNCTIONS = ("^use_.*$",)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class InvalidReactivityCause(Enum):
|
|
13
|
+
USE_AFTER_RETURN = "early return"
|
|
14
|
+
CONDITIONAL_USE = "conditional"
|
|
15
|
+
LOOP_USE = "loop"
|
|
16
|
+
NESTED_FUNCTION_USE = "nested function"
|
|
17
|
+
VARIABLE_ASSIGNMENT = "assignment"
|
|
18
|
+
EXCEPTION_USE = "exception"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
noqa_code_to_cause = {
|
|
22
|
+
"SH101": InvalidReactivityCause.USE_AFTER_RETURN,
|
|
23
|
+
"SH102": InvalidReactivityCause.CONDITIONAL_USE,
|
|
24
|
+
"SH103": InvalidReactivityCause.LOOP_USE,
|
|
25
|
+
"SH104": InvalidReactivityCause.NESTED_FUNCTION_USE,
|
|
26
|
+
"SH105": InvalidReactivityCause.VARIABLE_ASSIGNMENT,
|
|
27
|
+
"SH106": InvalidReactivityCause.EXCEPTION_USE,
|
|
28
|
+
}
|
|
29
|
+
cause_to_noqa_code = {v: k for k, v in noqa_code_to_cause.items()}
|
|
30
|
+
noqa_pattern = re.compile(r".*# noqa(?::\s*([A-Z]{2,3}\d{2,3})(?:,\s*([A-Z]{2,3}\d{2,3}))*)?")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if sys.version_info < (3, 11):
|
|
34
|
+
ScopeNodeType = t.Union[ast.For, ast.While, ast.If, ast.Try, ast.FunctionDef]
|
|
35
|
+
TryNodes = (ast.Try,)
|
|
36
|
+
else:
|
|
37
|
+
# except* nodes are only standardized in 3.11+
|
|
38
|
+
ScopeNodeType = t.Union[ast.For, ast.While, ast.If, ast.Try, ast.TryStar, ast.FunctionDef]
|
|
39
|
+
TryNodes = (ast.Try, ast.TryStar)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def line_to_noqa(line: str) -> t.Optional[t.Set[InvalidReactivityCause]]:
|
|
43
|
+
"""Return set of noqa code for which to not do quality assurance checks, or None when to do all qa."""
|
|
44
|
+
if "#" in line:
|
|
45
|
+
no_qa = set()
|
|
46
|
+
match = noqa_pattern.match(line)
|
|
47
|
+
if match is not None:
|
|
48
|
+
# not sure why we get some None values
|
|
49
|
+
groups = [k for k in match.groups() if k is not None]
|
|
50
|
+
if groups: # `noqa: SHXXX`` found
|
|
51
|
+
for group in groups:
|
|
52
|
+
if group not in noqa_code_to_cause:
|
|
53
|
+
if group.startswith("SH"):
|
|
54
|
+
raise ValueError(f"Unknown noqa code {group}")
|
|
55
|
+
else:
|
|
56
|
+
no_qa.add(noqa_code_to_cause[group])
|
|
57
|
+
else: # we found `noqa`
|
|
58
|
+
# skip all qa
|
|
59
|
+
no_qa = set(noqa_code_to_cause.values())
|
|
60
|
+
return no_qa
|
|
61
|
+
else:
|
|
62
|
+
return None # no noqa found
|
|
63
|
+
return None # fast path, no comment found
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def should_skip_qa(global_no_qa: t.Optional[t.Set[InvalidReactivityCause]], cause: InvalidReactivityCause, line: str) -> bool:
|
|
67
|
+
line_qa = line_to_noqa(line)
|
|
68
|
+
if global_no_qa is None and line_qa is None:
|
|
69
|
+
# there is not a noqa on the function or line
|
|
70
|
+
return False
|
|
71
|
+
else:
|
|
72
|
+
noqa = global_no_qa or set()
|
|
73
|
+
if line_qa is not None:
|
|
74
|
+
noqa |= line_qa
|
|
75
|
+
return cause in noqa
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class HookValidationError(Exception):
|
|
79
|
+
def __init__(self, cause: InvalidReactivityCause, message: str):
|
|
80
|
+
self.cause = cause
|
|
81
|
+
super().__init__(message)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class HookValidator(ast.NodeVisitor):
|
|
85
|
+
def __init__(self, component: t.Callable, use_functions=DEFAULT_USE_FUNCTIONS):
|
|
86
|
+
self.use_functions = [re.compile(use_func) for use_func in use_functions]
|
|
87
|
+
|
|
88
|
+
self.root_function_return: t.Optional[ast.Return] = None
|
|
89
|
+
self.outer_scope: t.Optional[ScopeNodeType] = None
|
|
90
|
+
|
|
91
|
+
self.filename = component.__code__.co_filename
|
|
92
|
+
self.line_offset = component.__code__.co_firstlineno - 1
|
|
93
|
+
self.component = component
|
|
94
|
+
|
|
95
|
+
self.source = inspect.getsource(self.component)
|
|
96
|
+
# lines before we dedent
|
|
97
|
+
self.lines = self.source.split("\n")
|
|
98
|
+
# dedent the source code to avoid indentation errors
|
|
99
|
+
parsed_source = inspect.cleandoc(self.source)
|
|
100
|
+
parsed = ast.parse(parsed_source)
|
|
101
|
+
# Get nodes from inside the function body
|
|
102
|
+
func_definition = t.cast(ast.FunctionDef, parsed.body[0])
|
|
103
|
+
self.function_scope: ast.FunctionDef = func_definition
|
|
104
|
+
self._root_function_scope = self.function_scope
|
|
105
|
+
# None means, *DO* qa
|
|
106
|
+
self.no_qa: t.Optional[t.Set[InvalidReactivityCause]] = None
|
|
107
|
+
|
|
108
|
+
def run(self):
|
|
109
|
+
import solara.settings
|
|
110
|
+
|
|
111
|
+
if solara.settings.main.check_hooks == "off":
|
|
112
|
+
return
|
|
113
|
+
func_def_line = self.lines[self._root_function_scope.lineno - 1]
|
|
114
|
+
self.no_qa = line_to_noqa(func_def_line)
|
|
115
|
+
try:
|
|
116
|
+
for node in self._root_function_scope.body:
|
|
117
|
+
self.visit(node)
|
|
118
|
+
except HookValidationError as e:
|
|
119
|
+
if solara.settings.main.check_hooks == "error":
|
|
120
|
+
raise e
|
|
121
|
+
elif solara.settings.main.check_hooks == "warn":
|
|
122
|
+
warnings.warn(str(e))
|
|
123
|
+
|
|
124
|
+
def matches_use_function(self, name: str) -> bool:
|
|
125
|
+
return any(use_func.match(name) for use_func in self.use_functions)
|
|
126
|
+
|
|
127
|
+
def node_to_scope_cause(self, node: ScopeNodeType) -> InvalidReactivityCause:
|
|
128
|
+
if isinstance(node, ast.If):
|
|
129
|
+
return InvalidReactivityCause.CONDITIONAL_USE
|
|
130
|
+
elif isinstance(node, (ast.For, ast.While)):
|
|
131
|
+
return InvalidReactivityCause.LOOP_USE
|
|
132
|
+
elif isinstance(node, ast.FunctionDef):
|
|
133
|
+
return InvalidReactivityCause.NESTED_FUNCTION_USE
|
|
134
|
+
elif isinstance(node, TryNodes):
|
|
135
|
+
return InvalidReactivityCause.EXCEPTION_USE
|
|
136
|
+
else:
|
|
137
|
+
warnings.warn(f"Unexpected scope node type: {node}, line={node.lineno}")
|
|
138
|
+
|
|
139
|
+
def visit_Call(self, node: ast.Call):
|
|
140
|
+
"""Records calls of use functions, i.e. solara.use_state(...)"""
|
|
141
|
+
func = node.func
|
|
142
|
+
if isinstance(func, ast.Call):
|
|
143
|
+
# Nested function, it will appear in another node later
|
|
144
|
+
return
|
|
145
|
+
if isinstance(func, ast.Name):
|
|
146
|
+
id_ = func.id
|
|
147
|
+
elif isinstance(func, ast.Attribute):
|
|
148
|
+
id_ = func.attr
|
|
149
|
+
else:
|
|
150
|
+
warnings.warn(f"Unexpected function node type: {func}, line={node.lineno}")
|
|
151
|
+
return
|
|
152
|
+
if self.matches_use_function(id_):
|
|
153
|
+
self.error_on_early_return(node, id_)
|
|
154
|
+
self.error_on_invalid_scope(node, id_)
|
|
155
|
+
self.generic_visit(node)
|
|
156
|
+
|
|
157
|
+
def visit_If(self, node: ast.If):
|
|
158
|
+
self._visit_children_using_scope(node)
|
|
159
|
+
|
|
160
|
+
def visit_For(self, node: ast.For):
|
|
161
|
+
self._visit_children_using_scope(node)
|
|
162
|
+
|
|
163
|
+
def visit_While(self, node: ast.While):
|
|
164
|
+
self._visit_children_using_scope(node)
|
|
165
|
+
|
|
166
|
+
def visit_Try(self, node: ast.Try) -> t.Any:
|
|
167
|
+
self._visit_children_using_scope(node)
|
|
168
|
+
|
|
169
|
+
if sys.version_info >= (3, 11):
|
|
170
|
+
|
|
171
|
+
def visit_TryStar(self, node: ast.TryStar) -> t.Any:
|
|
172
|
+
self._visit_children_using_scope(node)
|
|
173
|
+
|
|
174
|
+
def visit_FunctionDef(self, node: ast.FunctionDef):
|
|
175
|
+
old_function_scope = self.function_scope
|
|
176
|
+
self.function_scope = node
|
|
177
|
+
self._visit_children_using_scope(node)
|
|
178
|
+
self.function_scope = old_function_scope
|
|
179
|
+
|
|
180
|
+
def _visit_children_using_scope(self, node: ScopeNodeType):
|
|
181
|
+
outer_scope = self.outer_scope
|
|
182
|
+
self.outer_scope = node
|
|
183
|
+
for child in node.body:
|
|
184
|
+
self.visit(child)
|
|
185
|
+
self.outer_scope = outer_scope
|
|
186
|
+
|
|
187
|
+
def visit_Assign(self, node: ast.Assign):
|
|
188
|
+
self.error_on_invalid_assign(node)
|
|
189
|
+
self.generic_visit(node)
|
|
190
|
+
|
|
191
|
+
def visit_Return(self, node: ast.Return):
|
|
192
|
+
"""
|
|
193
|
+
Records the earliest return statement in the function
|
|
194
|
+
"""
|
|
195
|
+
# Returns are valid in nested functions
|
|
196
|
+
if self.function_scope != self._root_function_scope:
|
|
197
|
+
return
|
|
198
|
+
self.root_function_return = node
|
|
199
|
+
self.generic_visit(node)
|
|
200
|
+
|
|
201
|
+
def error_on_invalid_assign(self, node: ast.Assign):
|
|
202
|
+
if isinstance(node.value, ast.Attribute) and self.matches_use_function(node.value.attr):
|
|
203
|
+
line = node.lineno + self.line_offset
|
|
204
|
+
cause = InvalidReactivityCause.VARIABLE_ASSIGNMENT
|
|
205
|
+
message = f"Assigning the hook `{node.value.attr}` to a variable is not allowed since it complicates the tracking of valid hook use {_hint_supress(line, cause)}"
|
|
206
|
+
|
|
207
|
+
raise HookValidationError(
|
|
208
|
+
cause,
|
|
209
|
+
f"{self.get_source_context(line)}: {message}",
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def error_on_early_return(self, use_node: ast.Call, use_node_id: str):
|
|
213
|
+
"""
|
|
214
|
+
Checks if the latest use of a reactive function occurs after the earliest return
|
|
215
|
+
"""
|
|
216
|
+
if self.root_function_return and self.root_function_return.lineno <= use_node.lineno:
|
|
217
|
+
offset_return = self.root_function_return.lineno + self.line_offset
|
|
218
|
+
offset_use = use_node.lineno + self.line_offset
|
|
219
|
+
cause = InvalidReactivityCause.USE_AFTER_RETURN
|
|
220
|
+
line = self.lines[use_node.lineno - 1]
|
|
221
|
+
if should_skip_qa(self.no_qa, cause, line):
|
|
222
|
+
return
|
|
223
|
+
|
|
224
|
+
raise HookValidationError(
|
|
225
|
+
cause,
|
|
226
|
+
f"""{self.get_source_context(offset_use)}: `{use_node_id}` found despite early return on line {offset_return}
|
|
227
|
+
{_hint_supress(line, cause)}""",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
def error_on_invalid_scope(self, use_node: ast.Call, use_node_id: str):
|
|
231
|
+
"""
|
|
232
|
+
Checks if the latest use of a reactive function occurs after the earliest return or in an invalid scope
|
|
233
|
+
such as try-except blocks.
|
|
234
|
+
"""
|
|
235
|
+
if self.outer_scope is None:
|
|
236
|
+
return
|
|
237
|
+
offset_use = use_node.lineno + self.line_offset
|
|
238
|
+
cause = self.node_to_scope_cause(self.outer_scope)
|
|
239
|
+
|
|
240
|
+
scope_line = self.outer_scope.lineno + self.line_offset
|
|
241
|
+
line = self.lines[use_node.lineno - 1]
|
|
242
|
+
if should_skip_qa(self.no_qa, cause, line):
|
|
243
|
+
return
|
|
244
|
+
raise HookValidationError(
|
|
245
|
+
cause,
|
|
246
|
+
f"""{self.get_source_context(offset_use)}: `{use_node_id}` found within a {cause.value} created on line {scope_line}
|
|
247
|
+
{_hint_supress(line, cause)}""",
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def get_source_context(self, lineno):
|
|
251
|
+
return f"{self.filename}:{lineno}: {self.component.__qualname__}"
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _hint_supress(line, cause):
|
|
255
|
+
return f"""To suppress this check, replace the line with:
|
|
256
|
+
{line} # noqa: {cause_to_noqa_code[cause]}
|
|
257
|
+
|
|
258
|
+
Make sure you understand the consequences of this, by reading about the rules of hooks at:
|
|
259
|
+
https://solara.dev/documentation/advanced/understanding/rules-of-hooks
|
|
260
|
+
"""
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Solara Changelog
|
|
2
2
|
|
|
3
|
+
## Version 1.35.1
|
|
4
|
+
|
|
5
|
+
* Bug Fix: Vulnerability which allowed for accessing any file on the system. [CVE-2024-39903](https://nvd.nist.gov/vuln/detail/CVE-2024-39903)
|
|
6
|
+
[391e212](https://github.com/widgetti/solara/commit/391e2124a502622fe7a04321a540af6f252d8d4b).
|
|
7
|
+
* Bug Fix: On windows we now read all text files (like CSS) with utf8 encoding by default [681f69b](https://github.com/widgetti/solara/commit/681f69b7779da2bd3586d6e9fd0b21d36e6fb124)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Version 1.35.0
|
|
11
|
+
|
|
12
|
+
* Feature: On slower systems, pytest-ipywidgets could time out when connecting to the app, `PYTEST_IPYWIDGETS_SOLARA_APP_WAIT_TIMEOUT` can be set to increase the timeout. [c302caf](https://github.com/widgetti/solara/commit/c302caf520b6bd4825f4104ac4d629b1d9543607) and [Our testing docs](https://solara.dev/documentation/advanced/howto/testing).
|
|
13
|
+
* Bug Fix: The sidebar container did not have `height: 100%` set, causing the sidebar to not fill the entire height of the page. [5358f0f](https://github.com/widgetti/solara/commit/5358f0f1b8582422041bfc6553e6d95e103a9aa9)
|
|
14
|
+
* Bug Fix: Vulnerability which allowed for accessing any file on the system. [CVE-2024-39903](https://nvd.nist.gov/vuln/detail/CVE-2024-39903)
|
|
15
|
+
[391e212](https://github.com/widgetti/solara/commit/391e2124a502622fe7a04321a540af6f252d8d4b). A follow up fix was need for this in 1.35.1, we recommend upgrading to that version.
|
|
16
|
+
* Bug Fix: Allow the FileBrowser to navigate to the root on Windows [707](https://github.com/widgetti/solara/pull/707) - Contributed by Jochem Smit.
|
|
17
|
+
|
|
3
18
|
## Version 1.34.1
|
|
4
19
|
|
|
5
20
|
* Bug Fix: Using `SOLARA_ASSETS_PROXY=False` (default on [py.cafe](https://py.cafe)) would break grid layout. [#705](https://github.com/widgetti/solara/pull/705)
|
|
@@ -4,4 +4,189 @@ description: Learn the rules that govern the usage of hooks in your Solara appli
|
|
|
4
4
|
----
|
|
5
5
|
# Rules of hooks
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Hooks are Python function whose name start with `use_`. The most used hook is [use_state](https://solara.dev/documentation/api/hooks/use_state) which is used to manage the state of a component.
|
|
8
|
+
|
|
9
|
+
Hooks can only be called at the top level of a function component or a custom hook. They cannot be called inside loops, conditions, or nested functions.
|
|
10
|
+
The reason for this is that hooks rely on the order in which they are called to maintain state between renders. If a hook is called conditionally, it may not be called on every render, which can mix up the states.
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
import solara
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@solara.component
|
|
17
|
+
def Page():
|
|
18
|
+
x, set_x = solara.use_state(1) # state 'slot' 1
|
|
19
|
+
if x < 10:
|
|
20
|
+
y, set_y = solara.use_state(2) # state 'slot' 2
|
|
21
|
+
else:
|
|
22
|
+
y, set_y = solara.use_state("foo") # *also* state 'slot' 2
|
|
23
|
+
solara.Text("Done")
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
In the above example, the `use_state(2)` and `use_state("foo")` is called conditionally, which means that the state 'slot' 2 (meaning, `y` and `set_y`) sometimes refer to the integer `2` and sometimes to the string `"foo"`. This gives unexpected behavior and will lead to bugs.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
The rules of hooks are checked by Solara, if you break the rules, you will get a warning, and in the future (Solara v2.0) you will get an error (an exception is raised).
|
|
30
|
+
|
|
31
|
+
## Example
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import solara
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@solara.component
|
|
38
|
+
def Page():
|
|
39
|
+
for i in range(10):
|
|
40
|
+
solara.use_state(1)
|
|
41
|
+
solara.Text("Done")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Will give the warning:
|
|
45
|
+
```
|
|
46
|
+
/some/prefix/site-packages/solara/solara/validate_hooks.py:122: UserWarning: /my/app.py:56: Page: `use_state` found within a loop created on line 55
|
|
47
|
+
To suppress this check, replace the line with:
|
|
48
|
+
solara.use_state(1) # noqa: SH103
|
|
49
|
+
|
|
50
|
+
Make sure you understand the consequences of this, by reading about the rules of hooks at:
|
|
51
|
+
https://solara.dev/documentation/advanced/understanding/rules-of-hooks
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
As the warning suggests, you can suppress the warning by adding `# noqa: SH103` to the line that breaks the rules.
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import solara
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@solara.component
|
|
61
|
+
def Page():
|
|
62
|
+
for i in range(10):
|
|
63
|
+
solara.use_state(1) # noqa: SH103
|
|
64
|
+
|
|
65
|
+
solara.Text("Done")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The warning can also be suppressed for the whole component by adding `# noqa: SH103` to the function definition.
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import solara
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@solara.component
|
|
75
|
+
def Page(): # noqa: SH103
|
|
76
|
+
for i in range(10):
|
|
77
|
+
solara.use_state(1)
|
|
78
|
+
|
|
79
|
+
solara.Text("Done")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
However, we strongly advise against this, and you should only use it if you know what you are doing (knowing the the loop will always run a fixed amount of times).
|
|
84
|
+
|
|
85
|
+
If you want to have an error raised instead of a warning, you can set the environment variable `SOLARA_CHECK_HOOKS=raise`, this is
|
|
86
|
+
planned to be the default in Solara v2.0.
|
|
87
|
+
|
|
88
|
+
In that case, you should get an error like this:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
solara.validate_hooks.HookValidationError: /my/app.py:56: Page: `use_state` found within a loop created on line 55
|
|
93
|
+
To suppress this check, replace the line with:
|
|
94
|
+
solara.use_state(1) # noqa: SH103
|
|
95
|
+
|
|
96
|
+
Make sure you understand the consequences of this, by reading about the rules of hooks at:
|
|
97
|
+
https://solara.dev/documentation/advanced/understanding/rules-of-hooks
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
For convenience, you can also disable the check by setting the environment variable `SOLARA_CHECK_HOOKS=off`, but we strongly advise against this.
|
|
102
|
+
|
|
103
|
+
## Types of error
|
|
104
|
+
|
|
105
|
+
### Early return (SH101)
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
import solara
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@solara.component
|
|
112
|
+
def Page():
|
|
113
|
+
solara.Text("Done")
|
|
114
|
+
if x < 10:
|
|
115
|
+
return # will cause the below use_state to not always be called
|
|
116
|
+
solara.use_state(1)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Conditional use (SH102)
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
import solara
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@solara.component
|
|
126
|
+
def Page():
|
|
127
|
+
if x < 10:
|
|
128
|
+
solara.use_state(1) # will cause the use_state to not always be called
|
|
129
|
+
solara.Text("Done")
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
### Loop use (SH103)
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
import solara
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@solara.component
|
|
140
|
+
def Page():
|
|
141
|
+
for i in range(x):
|
|
142
|
+
solara.use_state(1) # will cause the use_state to not always be a constant number of times
|
|
143
|
+
solara.Text("Done")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Nested function use (SH104)
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
import solara
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@solara.component
|
|
153
|
+
def Page():
|
|
154
|
+
def inner():
|
|
155
|
+
solara.use_state(1) # will use_state always be called? Difficult to analyze, so don't do it
|
|
156
|
+
inner()
|
|
157
|
+
solara.Text("Done")
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
### Variable assignment (SH105)
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
import solara
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@solara.component
|
|
168
|
+
def Page():
|
|
169
|
+
x = solara.use_state
|
|
170
|
+
x(1) # will use_state always be called? Difficult to analyze, so don't do it
|
|
171
|
+
solara.Text("Done")
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
### Exception use (SH106)
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
import solara
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@solara.component
|
|
182
|
+
def Page():
|
|
183
|
+
try:
|
|
184
|
+
this_might_fail()
|
|
185
|
+
solara.use_state(1) # will use_state always be called? Difficult to analyze, so don't do it
|
|
186
|
+
except:
|
|
187
|
+
pass
|
|
188
|
+
solara.Text("Done")
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
For this reason, it's also advised to always call hooks first, before doing anything else in a function component that might raise
|
|
192
|
+
an exception.
|
|
@@ -1,13 +1,14 @@
|
|
|
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=
|
|
3
|
+
solara/__init__.py,sha256=HjA7dRCYxK9IPEpdZDijNyjgzTEooKYtj1r87kR4By4,3597
|
|
4
4
|
solara/__main__.py,sha256=hxMYlUg-t-KTOJSVfEHwvcTV51WHX6INpN0F_qaesUg,23672
|
|
5
5
|
solara/alias.py,sha256=9vfLzud77NP8in3OID9b5mmIO8NyrnFjN2_aE0lSb1k,216
|
|
6
|
-
solara/autorouting.py,sha256=
|
|
6
|
+
solara/autorouting.py,sha256=iQ-jP5H-kdu1uZyLEFeiHG1IsOLZLzwKVtQPyXSgGSM,23093
|
|
7
7
|
solara/cache.py,sha256=rZEW_xVIj3vvajntsQDnaglniTQ90izkX8vOqe1mMvE,10500
|
|
8
8
|
solara/checks.html,sha256=NZoefOKYpE6NHQJchi4WE5HkDG3xpJW0kY6TOAFHQtE,3304
|
|
9
9
|
solara/checks.py,sha256=WtMzUM1HN127juk5fFV2jdsJ1qT5Ghg21wEZfiVfIFc,7563
|
|
10
10
|
solara/comm.py,sha256=BhHFO1fBfFP0cOKxx_oKUr6-8UqpfGZbVaofYxIXrS8,765
|
|
11
|
+
solara/core.py,sha256=bB9OJAmFNsWFBGepRJaGaS12xtoY2-xahmBVD7CuvRU,1429
|
|
11
12
|
solara/datatypes.py,sha256=xHHjObTEf4Ar3e4Euvi0sA7UWDSbNTXuyLIf_0wcDs8,4186
|
|
12
13
|
solara/express.py,sha256=R0E2ewwL0m09KdoDNhF_ZF5TnC7ytty5rzM8hDykpGk,6919
|
|
13
14
|
solara/kitchensink.py,sha256=RUx3kW6CQAz9PMxB1sPI03IH5xJfsaaXq3w9bBuC6rg,249
|
|
@@ -17,10 +18,11 @@ solara/minisettings.py,sha256=YLLOnvoZsaICKfnQ8zxNHMhAEXq4HMVR_e-LAHLE2XQ,4852
|
|
|
17
18
|
solara/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
19
|
solara/reactive.py,sha256=GKp7agR3KtUAyLaxwcrMG9yYs_zO8KbpdslFk8Ata20,2940
|
|
19
20
|
solara/routing.py,sha256=G_iZKozdVoUuD-qSMyuPV6jeN4qBqujAUvekw036f88,9143
|
|
20
|
-
solara/settings.py,sha256=
|
|
21
|
-
solara/tasks.py,sha256=
|
|
21
|
+
solara/settings.py,sha256=fCi8q8QSrpL9vX8UnqNYJwyeTZ1OOyni9-uC_eaaTgU,2138
|
|
22
|
+
solara/tasks.py,sha256=4qV7HRAKCQ_POHUWkg8Rzx8hiFRO82P6ypgXKb6RUSo,30187
|
|
22
23
|
solara/toestand.py,sha256=cGVw1TAX_Kn9u2IN_GHhCPEOP8W3N0PwA-WHuvk8EgU,23570
|
|
23
24
|
solara/util.py,sha256=ex3Hggy-kTQa4DTEMDlhFTihzqnTfjxWCbHk0ihaZSM,8934
|
|
25
|
+
solara/validate_hooks.py,sha256=ZDrAgx3XDrmMU-zRtW1AgYzRbzeeag1t9j0RCC6Or6s,10081
|
|
24
26
|
solara/components/__init__.py,sha256=kVFPHiqv0-G_jQoL5NJnYskB9l9_2Y9jxVP3fFY8cVg,2636
|
|
25
27
|
solara/components/alert.py,sha256=sNjlrCu2niR6LD9gZFXwvSBMszCKt6nRH58kE7RgsDw,5144
|
|
26
28
|
solara/components/applayout.py,sha256=Q1n8foPT1szjIKqjUum6QxFnUvNj_MheMmyc8r7FIsI,16753
|
|
@@ -111,7 +113,7 @@ solara/server/kernel.py,sha256=3mwRRBw6BOcKLACL4fCUGgtI_RZ5KTSM1MlAtRlDbmA,11092
|
|
|
111
113
|
solara/server/kernel_context.py,sha256=RrNVAkoev6u6LZBvDfG86zyVs7eDVUsrp_4Au_FLlgY,16718
|
|
112
114
|
solara/server/patch.py,sha256=bwIlgXJSMUEk2eMTqIXaWG3es3WiAq3e2AilFMvrZKQ,18788
|
|
113
115
|
solara/server/reload.py,sha256=BBH7QhrV1-e9RVyNE3uz1oPj1DagC3t_XSqGPNz0nJE,9747
|
|
114
|
-
solara/server/server.py,sha256=
|
|
116
|
+
solara/server/server.py,sha256=aje6QmZV1ikpqSAuI_ksOWcdOkz6Za4-xCogE1g2qRw,16380
|
|
115
117
|
solara/server/settings.py,sha256=8QpVW_hYe4QvSVvDMeobpUEFa_jjCAGrSKgCGzjZ3As,7340
|
|
116
118
|
solara/server/shell.py,sha256=xKox0fvDxdcWleE8p2ffCkihvjLJsWn2FujMbgUjYn0,8677
|
|
117
119
|
solara/server/starlette.py,sha256=zOyE5cdsE-JVvXuoKWv2nnkWHeUBVV3b6i9-3sK0utI,23866
|
|
@@ -135,7 +137,7 @@ solara/server/static/highlight-dark.css,sha256=gmC3pr3x2BqJgTswNoN6AkqfEhBk24hPF
|
|
|
135
137
|
solara/server/static/highlight.css,sha256=k8ZdT5iwrGQ5tXTQHAXuxvZrSUq3kwCdEpy3mlFoZjs,2637
|
|
136
138
|
solara/server/static/main-vuetify.js,sha256=-FLlUqclZdVhCXsqawzpxtQ9vpaDA6KQdwoDKJCr_AI,8926
|
|
137
139
|
solara/server/static/main.js,sha256=mcx4JNQ4Lg4pNdUIqMoZos1mZyYFS48yd_JNFFJUqIE,5679
|
|
138
|
-
solara/server/static/solara_bootstrap.py,sha256=
|
|
140
|
+
solara/server/static/solara_bootstrap.py,sha256=eYLM3dK8Lh-VX9YRChPnv1mHg7LIN9tbRQzGi3bXr6A,3195
|
|
139
141
|
solara/server/static/sun.svg,sha256=jEKBAGCr7b9zNYv0VUb7lMWKjnU2dX69_Ye_DZWGXJI,6855
|
|
140
142
|
solara/server/static/webworker.js,sha256=cjAFz7-SygStHJnYlJUlJs-gE_7YQeQ-WBDcmKYyjvo,1372
|
|
141
143
|
solara/server/templates/index.html.j2,sha256=JXQo1M-STFHLBOFetgG7509cAq8xUP0VAEtYDzz35fY,31
|
|
@@ -144,7 +146,7 @@ solara/server/templates/loader-plain.html,sha256=VEtMDC8MQj75o2iWJ_gR70Jp05oOoyR
|
|
|
144
146
|
solara/server/templates/loader-solara.css,sha256=QerfzwlenkSJMq8uk_qEtoSdcI-DKMRrH9XXDEPsrUA,1670
|
|
145
147
|
solara/server/templates/loader-solara.html,sha256=bgp3GHOAhfzU0rcAAYDsqhGlemfZo4YNhRsYIvhU7YM,2726
|
|
146
148
|
solara/server/templates/plain.html,sha256=yO1r2hidA3bxOclaxtI_vTZtdDTFn2KKeeoldJuinXk,2762
|
|
147
|
-
solara/server/templates/solara.html.j2,sha256=
|
|
149
|
+
solara/server/templates/solara.html.j2,sha256=Lgurpn7U7eByfNPTasxU2VF24HLbOV6gbdYk0y618pw,20253
|
|
148
150
|
solara/template/button.py,sha256=HM382prl4bNLF8sLBd9Sh43ReMGedG_z_h4XN4mDYRI,311
|
|
149
151
|
solara/template/markdown.py,sha256=31G3ezFooiveSrUH5afz5_nJ8SStjbx-_x5YLXv_hPk,1137
|
|
150
152
|
solara/template/portal/.flake8,sha256=wxWLwamdP33Jm1mPa1sVe3uT0AZkG_wHPbY3Ci7j5-g,136
|
|
@@ -204,7 +206,7 @@ solara/website/pages/apps/multipage/__init__.py,sha256=zljTAXbsV8hqb67dwmx_PhzN-
|
|
|
204
206
|
solara/website/pages/apps/multipage/page1.py,sha256=5hK0RZ8UBBOaZcPKaplbLeb0VvaerhB6m3Jn5C0afRM,872
|
|
205
207
|
solara/website/pages/apps/multipage/page2.py,sha256=uRJ8YPFyKy7GR_Ii8DJSx3akb3H15rQAJZETMt9jVEk,1422
|
|
206
208
|
solara/website/pages/changelog/__init__.py,sha256=iBCpD5LVrFxQtEHX116u_6vqNBktZD3AXR65eTZblFo,227
|
|
207
|
-
solara/website/pages/changelog/changelog.md,sha256=
|
|
209
|
+
solara/website/pages/changelog/changelog.md,sha256=A0dvns56FNz9QRDes68GSSZcUa0ZvSucxBlJJZ8Kisk,17676
|
|
208
210
|
solara/website/pages/contact/__init__.py,sha256=L6o45fHAMClqyWdHWxI6JuiwUTP-U-yXCgd3lxMUcxA,223
|
|
209
211
|
solara/website/pages/contact/contact.md,sha256=zp66RGhOE_tdkRaWKZqGbuk-PnOqBWhDVvX5zSLXoHw,798
|
|
210
212
|
solara/website/pages/documentation/__init__.py,sha256=zJh3kmr6OhFPbax8Igp-LN-m9cZNrs9sq9ifFuC8Kic,6003
|
|
@@ -231,7 +233,7 @@ solara/website/pages/documentation/advanced/content/20-understanding/06-ipyvueti
|
|
|
231
233
|
solara/website/pages/documentation/advanced/content/20-understanding/10-reacton.md,sha256=sK1nFnthUfV8iDk1e4g65gnTuFbrcbLPR-y0MOCRn6I,1397
|
|
232
234
|
solara/website/pages/documentation/advanced/content/20-understanding/12-reacton-basics.md,sha256=Mq_Pe2a5p9g4t2JkJzRMLtpYjeHZgcKIzPAbwblrye4,5328
|
|
233
235
|
solara/website/pages/documentation/advanced/content/20-understanding/15-anatomy.md,sha256=luqGGRsTsDl8D3JH49BZR_AKtnu2Rfpc1f4EWfxWE8c,1984
|
|
234
|
-
solara/website/pages/documentation/advanced/content/20-understanding/17-rules-of-hooks.md,sha256=
|
|
236
|
+
solara/website/pages/documentation/advanced/content/20-understanding/17-rules-of-hooks.md,sha256=A-wHZQwNPcZg1S_hcaE36VG24H1Hx_HXP4jEuWNR5zk,5088
|
|
235
237
|
solara/website/pages/documentation/advanced/content/20-understanding/18-containers.md,sha256=Bkl22GXxO0MhTVpUK1q0l5h_wBWNgkR1ZoCG28p7r40,4905
|
|
236
238
|
solara/website/pages/documentation/advanced/content/20-understanding/20-solara.md,sha256=sNK-mmgbj9rDHOXu5JaP7OMQdX0chOZ72-aMdOEOkug,751
|
|
237
239
|
solara/website/pages/documentation/advanced/content/20-understanding/40-routing.md,sha256=7-efGbXIVhUkLCFtG9rCBxKcDcQMEt_GMyt4GDCHRK8,9669
|
|
@@ -434,9 +436,9 @@ solara/widgets/vue/gridlayout.vue,sha256=nFZJotdznqI9tUYZ1Elv9OLA0adazxvVZAggMHH
|
|
|
434
436
|
solara/widgets/vue/html.vue,sha256=48K5rjp0AdJDeRV6F3nOHW3J0WXPeHn55r5pGClK2fU,112
|
|
435
437
|
solara/widgets/vue/navigator.vue,sha256=SLrzBI0Eiys-7maXA4e8yyD13-O5b4AnCGE9wKuJDHE,3646
|
|
436
438
|
solara/widgets/vue/vegalite.vue,sha256=E3dlfhR-Ol7nqQZN6wCZC_3Tz98CJW0i_EU39mj0XHw,3986
|
|
437
|
-
solara_ui-1.
|
|
438
|
-
solara_ui-1.
|
|
439
|
-
solara_ui-1.
|
|
440
|
-
solara_ui-1.
|
|
441
|
-
solara_ui-1.
|
|
442
|
-
solara_ui-1.
|
|
439
|
+
solara_ui-1.37.0.data/data/etc/jupyter/jupyter_notebook_config.d/solara.json,sha256=3UhTBQi6z7F7pPjmqXxfddv79c8VGR9H7zStDLp6AwY,115
|
|
440
|
+
solara_ui-1.37.0.data/data/etc/jupyter/jupyter_server_config.d/solara.json,sha256=D9J-rYxAzyD5GOqWvuPjacGUVFHsYtTfZ4FUbRzRvIA,113
|
|
441
|
+
solara_ui-1.37.0.dist-info/METADATA,sha256=YUljmgDsZzPGcZAxZi2L_uB4sjIZtLgDIw4Zm5g7g0g,7284
|
|
442
|
+
solara_ui-1.37.0.dist-info/WHEEL,sha256=L5_n4Kc1NmrSdVgbp6hdnwwVwBnoYOCnbHBRMD-qNJ4,105
|
|
443
|
+
solara_ui-1.37.0.dist-info/licenses/LICENSE,sha256=fFJUz-CWzZ9nEc4QZKu44jMEoDr5fEW-SiqljKpD82E,1086
|
|
444
|
+
solara_ui-1.37.0.dist-info/RECORD,,
|
|
File without changes
|
{solara_ui-1.35.1.data → solara_ui-1.37.0.data}/data/etc/jupyter/jupyter_server_config.d/solara.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|