solara 1.29.1__py2.py3-none-any.whl → 1.30.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.
- solara/__init__.py +5 -5
- solara/__main__.py +6 -2
- solara/autorouting.py +88 -56
- solara/cache.py +2 -2
- solara/checks.html +1 -1
- solara/components/__init__.py +1 -1
- solara/components/applayout.py +5 -5
- solara/components/button.py +4 -4
- solara/components/card.py +1 -2
- solara/components/component_vue.py +9 -8
- solara/components/cross_filter.py +6 -7
- solara/components/datatable.py +2 -3
- solara/components/figure_altair.py +1 -1
- solara/components/file_download.py +2 -2
- solara/components/file_drop.py +76 -33
- solara/components/file_drop.vue +24 -10
- solara/components/head.py +1 -2
- solara/components/head_tag.py +2 -3
- solara/components/image.py +1 -1
- solara/components/link.py +3 -4
- solara/components/meta.py +1 -1
- solara/components/misc.py +5 -9
- solara/datatypes.py +2 -2
- solara/hooks/use_reactive.py +2 -2
- solara/lab/components/confirmation_dialog.py +1 -1
- solara/lab/components/tabs.py +6 -6
- solara/reactive.py +1 -1
- solara/routing.py +9 -8
- solara/scope/__init__.py +3 -2
- solara/server/app.py +44 -1
- solara/server/assets/style.css +6 -0
- solara/server/esm.py +28 -4
- solara/server/kernel_context.py +78 -7
- solara/server/patch.py +3 -0
- solara/server/reload.py +2 -2
- solara/server/server.py +3 -3
- solara/server/settings.py +1 -0
- solara/server/starlette.py +66 -33
- solara/server/static/solara_bootstrap.py +1 -1
- solara/server/templates/solara.html.j2 +62 -42
- solara/tasks.py +1 -6
- solara/util.py +23 -1
- solara/website/assets/custom.css +56 -0
- solara/website/components/__init__.py +1 -0
- solara/website/components/algolia_api.vue +157 -0
- solara/website/components/docs.py +118 -0
- solara/website/components/header.py +20 -10
- solara/website/components/hero.py +1 -1
- solara/website/components/markdown.py +30 -0
- solara/website/pages/__init__.py +234 -20
- solara/website/pages/apps/jupyter-dashboard-1.py +1 -1
- solara/website/pages/apps/multipage/__init__.py +1 -1
- solara/website/pages/apps/multipage/page2.py +1 -1
- solara/website/pages/apps/scatter.py +21 -7
- solara/website/pages/changelog/__init__.py +8 -0
- solara/website/pages/{docs/content/95-changelog.md → changelog/changelog.md} +43 -2
- solara/website/pages/contact/__init__.py +8 -0
- solara/website/pages/documentation/__init__.py +184 -0
- solara/website/pages/{docs → documentation/advanced}/__init__.py +2 -2
- solara/website/pages/documentation/advanced/content/00-overview.md +1 -0
- solara/website/pages/{docs → documentation/advanced}/content/10-howto/00-overview.md +5 -0
- solara/website/pages/{docs/content/10-howto/20-multipage.md → documentation/advanced/content/10-howto/10-multipage.md} +9 -5
- solara/website/pages/{docs/content/10-howto/30-layout.md → documentation/advanced/content/10-howto/20-layout.md} +26 -21
- solara/website/pages/{docs/content/10-howto/50-testing.md → documentation/advanced/content/10-howto/30-testing.md} +5 -0
- solara/website/pages/{docs/content/10-howto/51-debugging.md → documentation/advanced/content/10-howto/31-debugging.md} +4 -1
- solara/website/pages/{docs/content/10-howto/80-embed.md → documentation/advanced/content/10-howto/40-embed.md} +7 -1
- solara/website/pages/{docs/content/10-howto/ipywidget_libraries.md → documentation/advanced/content/10-howto/50-ipywidget_libraries.md} +4 -0
- solara/website/pages/documentation/advanced/content/20-understanding/00-introduction.md +10 -0
- solara/website/pages/{docs → documentation/advanced}/content/20-understanding/05-ipywidgets.md +5 -0
- solara/website/pages/{docs → documentation/advanced}/content/20-understanding/06-ipyvuetify.md +5 -1
- solara/website/pages/{docs → documentation/advanced}/content/20-understanding/10-reacton.md +4 -0
- solara/website/pages/{docs → documentation/advanced}/content/20-understanding/12-reacton-basics.md +5 -1
- solara/website/pages/{docs → documentation/advanced}/content/20-understanding/15-anatomy.md +6 -2
- solara/website/pages/documentation/advanced/content/20-understanding/17-rules-of-hooks.md +7 -0
- solara/website/pages/{docs → documentation/advanced}/content/20-understanding/18-containers.md +9 -3
- solara/website/pages/{docs → documentation/advanced}/content/20-understanding/20-solara.md +5 -0
- solara/website/pages/{docs → documentation/advanced}/content/20-understanding/40-routing.md +13 -9
- solara/website/pages/{docs → documentation/advanced}/content/20-understanding/50-solara-server.md +6 -1
- solara/website/pages/{docs → documentation/advanced}/content/20-understanding/60-voila.md +5 -0
- solara/website/pages/{docs/content/50-enterprise → documentation/advanced/content/30-enterprise}/10-oauth.md +7 -3
- solara/website/pages/{docs/content/10-howto → documentation/advanced/content/40-development}/01-contribute.md +9 -5
- solara/website/pages/{docs/content/90-development → documentation/advanced/content/40-development}/10-setup.md +6 -2
- solara/website/pages/documentation/api/__init__.py +19 -0
- solara/website/pages/documentation/api/cross_filter/__init__.py +9 -0
- solara/website/pages/documentation/api/hooks/__init__.py +9 -0
- solara/website/pages/{api → documentation/api/hooks}/use_effect.md +3 -3
- solara/website/pages/{api → documentation/api/hooks}/use_effect.py +2 -1
- solara/website/pages/{api → documentation/api/hooks}/use_memo.md +2 -2
- solara/website/pages/{api → documentation/api/hooks}/use_memo.py +2 -1
- solara/website/pages/{api → documentation/api/hooks}/use_reactive.py +1 -2
- solara/website/pages/{api → documentation/api/hooks}/use_state.py +1 -2
- solara/website/pages/documentation/api/routing/__init__.py +9 -0
- solara/website/pages/{api → documentation/api/routing}/generate_routes.py +1 -2
- solara/website/pages/{api → documentation/api/routing}/generate_routes_directory.py +1 -2
- solara/website/pages/{api → documentation/api/routing}/use_route.py +2 -2
- solara/website/pages/{api → documentation/api/routing}/use_router.py +1 -2
- solara/website/pages/documentation/api/utilities/__init__.py +9 -0
- solara/website/pages/{api → documentation/api/utilities}/component_vue.py +1 -2
- solara/website/pages/{api → documentation/api/utilities}/computed.py +2 -2
- solara/website/pages/{api → documentation/api/utilities}/display.py +1 -2
- solara/website/pages/{api → documentation/api/utilities}/get_kernel_id.py +2 -2
- solara/website/pages/{api → documentation/api/utilities}/get_session_id.py +2 -2
- solara/website/pages/{api → documentation/api/utilities}/on_kernel_start.py +9 -3
- solara/website/pages/{api → documentation/api/utilities}/reactive.py +1 -2
- solara/website/pages/{api → documentation/api/utilities}/widget.py +2 -2
- solara/website/pages/documentation/components/__init__.py +12 -0
- solara/website/pages/documentation/components/advanced/__init__.py +9 -0
- solara/website/pages/documentation/components/data/__init__.py +9 -0
- solara/website/pages/documentation/components/enterprise/__init__.py +9 -0
- solara/website/pages/{api → documentation/components/enterprise}/avatar.py +1 -2
- solara/website/pages/{api → documentation/components/enterprise}/avatar_menu.py +1 -2
- solara/website/pages/documentation/components/input/__init__.py +9 -0
- solara/website/pages/{api → documentation/components/input}/checkbox.py +1 -2
- solara/website/pages/documentation/components/input/file_drop.py +75 -0
- solara/website/pages/{api → documentation/components/input}/input.py +1 -2
- solara/website/pages/{api → documentation/components/input}/select.py +1 -2
- solara/website/pages/{api → documentation/components/input}/slider.py +1 -2
- solara/website/pages/{api → documentation/components/input}/switch.py +1 -2
- solara/website/pages/{api → documentation/components/input}/togglebuttons.py +1 -2
- solara/website/pages/documentation/components/lab/__init__.py +9 -0
- solara/website/pages/{api → documentation/components/lab}/chat.py +2 -3
- solara/website/pages/{api → documentation/components/lab}/cookies_headers.py +1 -1
- solara/website/pages/{api → documentation/components/lab}/input_date.py +1 -2
- solara/website/pages/{api → documentation/components/lab}/menu.py +1 -2
- solara/website/pages/{api → documentation/components/lab}/task.py +1 -2
- solara/website/pages/{api → documentation/components/lab}/theming.py +1 -2
- solara/website/pages/{api → documentation/components/lab}/use_task.py +1 -2
- solara/website/pages/documentation/components/layout/__init__.py +9 -0
- solara/website/pages/{api → documentation/components/layout}/app_bar.py +1 -2
- solara/website/pages/{api → documentation/components/layout}/app_bar_title.py +1 -2
- solara/website/pages/{api → documentation/components/layout}/card.py +1 -2
- solara/website/pages/{api → documentation/components/layout}/card_actions.py +1 -2
- solara/website/pages/{api → documentation/components/layout}/griddraggable.py +1 -1
- solara/website/pages/{api → documentation/components/layout}/gridfixed.py +1 -1
- solara/website/pages/{api → documentation/components/layout}/hbox.py +1 -1
- solara/website/pages/{api → documentation/components/layout}/vbox.py +1 -1
- solara/website/pages/documentation/components/output/__init__.py +9 -0
- solara/website/pages/{api → documentation/components/output}/file_download.py +1 -2
- solara/website/pages/{api → documentation/components/output}/image.py +1 -2
- solara/website/pages/{api → documentation/components/output}/tooltip.py +1 -2
- solara/website/pages/documentation/components/page/__init__.py +9 -0
- solara/website/pages/documentation/components/status/__init__.py +9 -0
- solara/website/pages/{api → documentation/components/status}/error.py +3 -3
- solara/website/pages/{api → documentation/components/status}/info.py +3 -3
- solara/website/pages/{api → documentation/components/status}/progress.py +1 -2
- solara/website/pages/{api → documentation/components/status}/spinner.py +1 -2
- solara/website/pages/{api → documentation/components/status}/success.py +3 -3
- solara/website/pages/{api → documentation/components/status}/warning.py +3 -3
- solara/website/pages/documentation/components/viz/__init__.py +9 -0
- solara/website/pages/{api → documentation/components/viz}/plotly.py +1 -0
- solara/website/pages/{examples → documentation/examples}/__init__.py +3 -43
- solara/website/pages/{examples → documentation/examples}/general/custom_storage.py +1 -1
- solara/website/pages/{examples → documentation/examples}/general/deploy_model.py +3 -3
- solara/website/pages/{examples → documentation/examples}/general/login_oauth.py +1 -1
- solara/website/pages/{examples → documentation/examples}/general/vue_component.py +1 -2
- solara/website/pages/{examples → documentation/examples}/libraries/altair.py +2 -3
- solara/website/pages/{examples → documentation/examples}/libraries/ipyleaflet_advanced.py +1 -1
- solara/website/pages/{examples → documentation/examples}/utilities/countdown_timer.py +1 -1
- solara/website/pages/{examples → documentation/examples}/visualization/plotly.py +1 -2
- solara/website/pages/documentation/faq/__init__.py +12 -0
- solara/website/pages/{docs → documentation/faq}/content/99-faq.md +4 -1
- solara/website/pages/documentation/getting_started/__init__.py +9 -0
- solara/website/pages/{docs/content/03-quickstart.md → documentation/getting_started/content/00-quickstart.md} +7 -2
- solara/website/pages/{docs/content/00-introduction.md → documentation/getting_started/content/01-introduction.md} +20 -16
- solara/website/pages/{docs → documentation/getting_started}/content/02-installing.md +5 -1
- solara/website/pages/documentation/getting_started/content/04-tutorials/00-overview.md +14 -0
- solara/website/pages/{docs/content/04-tutorial → documentation/getting_started/content/04-tutorials}/20-web-app.md +9 -4
- solara/website/pages/{docs/content/04-tutorial → documentation/getting_started/content/04-tutorials}/30-ipywidgets.md +13 -9
- solara/website/pages/{docs/content/04-tutorial → documentation/getting_started/content/04-tutorials}/40-streamlit.md +17 -12
- solara/website/pages/{docs/content/04-tutorial → documentation/getting_started/content/04-tutorials}/50-dash.md +7 -3
- solara/website/pages/{docs/content/04-tutorial → documentation/getting_started/content/04-tutorials}/_data_science.ipynb +5 -5
- solara/website/pages/documentation/getting_started/content/05-fundamentals/00-overview.md +11 -0
- solara/website/pages/{docs/content/07-fundamentals → documentation/getting_started/content/05-fundamentals}/10-components.md +7 -2
- solara/website/pages/{docs/content/07-fundamentals → documentation/getting_started/content/05-fundamentals}/50-state-management.md +8 -3
- solara/website/pages/documentation/getting_started/content/06-reference/00-overview.md +3 -0
- solara/website/pages/{docs/content/15-reference → documentation/getting_started/content/06-reference}/40-static_files.md +4 -0
- solara/website/pages/{docs/content/15-reference → documentation/getting_started/content/06-reference}/41-asset-files.md +5 -1
- solara/website/pages/{docs/content/15-reference → documentation/getting_started/content/06-reference}/60-static-site-generation.md +5 -1
- solara/website/pages/{docs/content/15-reference → documentation/getting_started/content/06-reference}/70-search.md +5 -1
- solara/website/pages/{docs/content/15-reference → documentation/getting_started/content/06-reference}/80-reloading.md +7 -3
- solara/website/pages/{docs/content/15-reference → documentation/getting_started/content/06-reference}/90-notebook-support.md +4 -1
- solara/website/pages/{docs/content/15-reference → documentation/getting_started/content/06-reference}/95-caching.md +6 -1
- solara/website/pages/documentation/getting_started/content/07-deploying/00-overview.md +7 -0
- solara/website/pages/{docs/content/30-deploying → documentation/getting_started/content/07-deploying}/10-self-hosted.md +7 -3
- solara/website/pages/{docs/content/30-deploying → documentation/getting_started/content/07-deploying}/20-cloud-hosted.md +5 -1
- solara/website/pages/documentation/getting_started/content/80-what-is-lab.md +7 -0
- solara/website/pages/{docs → documentation/getting_started}/content/90-troubleshoot.md +4 -0
- solara/website/pages/showcase/__init__.py +1 -1
- solara/website/pages/showcase/domino_code_assist.py +1 -1
- solara/website/pages/showcase/solara_dev.py +1 -1
- solara/website/public/social/discord.svg +1 -0
- solara/website/public/social/github.svg +1 -0
- solara/website/public/social/twitter.svg +3 -0
- {solara-1.29.1.dist-info → solara-1.30.1.dist-info}/METADATA +8 -7
- solara-1.30.1.dist-info/RECORD +437 -0
- {solara-1.29.1.dist-info → solara-1.30.1.dist-info}/WHEEL +1 -1
- solara/website/pages/api/__init__.py +0 -292
- solara/website/pages/api/default_layout.py +0 -16
- solara/website/pages/api/file_drop.py +0 -36
- solara/website/pages/docs/content/04-tutorial/00-overview.md +0 -9
- solara/website/pages/docs/content/07-fundamentals/00-overview.md +0 -7
- solara/website/pages/docs/content/15-reference/00-overview.md +0 -6
- solara/website/pages/docs/content/20-understanding/00-introduction.md +0 -4
- solara/website/pages/docs/content/20-understanding/17-rules-of-hooks.md +0 -3
- solara/website/pages/docs/content/30-deploying/00-overview.md +0 -3
- solara/website/pages/docs/content/lab/00-what-is-lab.md +0 -3
- solara-1.29.1.dist-info/RECORD +0 -411
- /solara/website/pages/{docs/content/99-contact.md → contact/contact.md} +0 -0
- /solara/website/pages/{docs/content/50-enterprise → documentation/advanced/content/30-enterprise}/00-overview.md +0 -0
- /solara/website/pages/{docs/content/__init__.py → documentation/advanced/content/40-development/00-overview.md} +0 -0
- /solara/website/pages/{api → documentation/api/cross_filter}/cross_filter_dataframe.py +0 -0
- /solara/website/pages/{api → documentation/api/cross_filter}/cross_filter_report.py +0 -0
- /solara/website/pages/{api → documentation/api/cross_filter}/cross_filter_select.py +0 -0
- /solara/website/pages/{api → documentation/api/cross_filter}/cross_filter_slider.py +0 -0
- /solara/website/pages/{api → documentation/api/hooks}/use_cross_filter.py +0 -0
- /solara/website/pages/{api → documentation/api/hooks}/use_dark_effective.py +0 -0
- /solara/website/pages/{api → documentation/api/hooks}/use_exception.py +0 -0
- /solara/website/pages/{api → documentation/api/hooks}/use_previous.py +0 -0
- /solara/website/pages/{api → documentation/api/hooks}/use_state_or_update.py +0 -0
- /solara/website/pages/{api → documentation/api/hooks}/use_thread.md +0 -0
- /solara/website/pages/{api → documentation/api/hooks}/use_thread.py +0 -0
- /solara/website/pages/{api → documentation/api/hooks}/use_trait_observe.py +0 -0
- /solara/website/pages/{api → documentation/api/routing}/resolve_path.py +0 -0
- /solara/website/pages/{api → documentation/api/routing}/route.py +0 -0
- /solara/website/pages/{api → documentation/api/utilities}/memoize.py +0 -0
- /solara/website/pages/{api → documentation/components/advanced}/link.py +0 -0
- /solara/website/pages/{api → documentation/components/advanced}/meta.py +0 -0
- /solara/website/pages/{api → documentation/components/advanced}/style.py +0 -0
- /solara/website/pages/{api → documentation/components}/common.py +0 -0
- /solara/website/pages/{api → documentation/components/data}/dataframe.py +0 -0
- /solara/website/pages/{api → documentation/components/data}/pivot_table.py +0 -0
- /solara/website/pages/{api → documentation/components/input}/button.py +0 -0
- /solara/website/pages/{api → documentation/components/input}/file_browser.py +0 -0
- /solara/website/pages/{api → documentation/components/lab}/confirmation_dialog.py +0 -0
- /solara/website/pages/{api → documentation/components/lab}/tab.py +0 -0
- /solara/website/pages/{api → documentation/components/lab}/tabs.py +0 -0
- /solara/website/pages/{api → documentation/components/layout}/app_layout.py +0 -0
- /solara/website/pages/{api → documentation/components/layout}/column.py +0 -0
- /solara/website/pages/{api → documentation/components/layout}/columns.py +0 -0
- /solara/website/pages/{api → documentation/components/layout}/columns_responsive.py +0 -0
- /solara/website/pages/{api → documentation/components/layout}/row.py +0 -0
- /solara/website/pages/{api → documentation/components/layout}/sidebar.py +0 -0
- /solara/website/pages/{api → documentation/components/output}/html.py +0 -0
- /solara/website/pages/{api → documentation/components/output}/markdown.py +0 -0
- /solara/website/pages/{api → documentation/components/output}/markdown_editor.py +0 -0
- /solara/website/pages/{api → documentation/components/output}/sql_code.py +0 -0
- /solara/website/pages/{api → documentation/components/page}/head.py +0 -0
- /solara/website/pages/{api → documentation/components/page}/title.py +0 -0
- /solara/website/pages/{api → documentation/components/viz}/altair.py +0 -0
- /solara/website/pages/{api → documentation/components/viz}/echarts.py +0 -0
- /solara/website/pages/{api → documentation/components/viz}/matplotlib.py +0 -0
- /solara/website/pages/{api → documentation/components/viz}/plotly_express.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/ai/__init__.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/ai/chatbot.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/ai/tokenizer.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/basics/__init__.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/basics/sine.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/fullscreen/__init__.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/fullscreen/authorization.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/fullscreen/layout_demo.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/fullscreen/multipage.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/fullscreen/scatter.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/fullscreen/scrolling.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/fullscreen/tutorial_streamlit.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/general/__init__.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/general/live_update.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/general/mycard.vue +0 -0
- /solara/website/pages/{examples → documentation/examples}/general/pokemon_search.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/ipycanvas.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/libraries/__init__.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/libraries/bqplot.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/libraries/ipyleaflet.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/utilities/__init__.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/utilities/calculator.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/utilities/todo.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/visualization/__init__.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/visualization/annotator.py +0 -0
- /solara/website/pages/{examples → documentation/examples}/visualization/linked_views.py +0 -0
- /solara/website/pages/{docs/content/04-tutorial → documentation/getting_started/content/04-tutorials}/10_data_science.py +0 -0
- /solara/website/pages/{docs/content/04-tutorial → documentation/getting_started/content/04-tutorials}/60-jupyter-dashboard-part1.py +0 -0
- /solara/website/pages/{docs/content/04-tutorial → documentation/getting_started/content/04-tutorials}/SF_crime_sample.csv.gz +0 -0
- /solara/website/pages/{docs/content/04-tutorial → documentation/getting_started/content/04-tutorials}/_jupyter_dashboard_1.ipynb +0 -0
- {solara-1.29.1.data → solara-1.30.1.data}/data/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
- {solara-1.29.1.data → solara-1.30.1.data}/data/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
- {solara-1.29.1.dist-info → solara-1.30.1.dist-info}/entry_points.txt +0 -0
- {solara-1.29.1.dist-info → solara-1.30.1.dist-info}/licenses/LICENSE +0 -0
solara/server/kernel_context.py
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
import contextvars
|
|
5
|
+
except ModuleNotFoundError:
|
|
6
|
+
contextvars = None # type: ignore
|
|
7
|
+
|
|
2
8
|
import dataclasses
|
|
3
9
|
import enum
|
|
10
|
+
import inspect
|
|
4
11
|
import logging
|
|
5
12
|
import os
|
|
6
13
|
import pickle
|
|
7
14
|
import threading
|
|
8
15
|
import time
|
|
16
|
+
import typing
|
|
9
17
|
from pathlib import Path
|
|
10
|
-
from
|
|
18
|
+
from types import FrameType, ModuleType
|
|
19
|
+
from typing import Any, Callable, Dict, List, NamedTuple, Optional, cast
|
|
11
20
|
|
|
12
21
|
import ipywidgets as widgets
|
|
13
22
|
import reacton
|
|
@@ -36,11 +45,46 @@ class PageStatus(enum.Enum):
|
|
|
36
45
|
CLOSED = "closed"
|
|
37
46
|
|
|
38
47
|
|
|
39
|
-
|
|
48
|
+
class _on_kernel_callback_entry(NamedTuple):
|
|
49
|
+
callback: Callable[[], Optional[Callable[[], None]]]
|
|
50
|
+
callpoint: Optional[Path]
|
|
51
|
+
module: Optional[ModuleType]
|
|
52
|
+
cleanup: Callable[[], None]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
_on_kernel_start_callbacks: List[_on_kernel_callback_entry] = []
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _find_root_module_frame() -> Optional[FrameType]:
|
|
59
|
+
# basically the module where the call stack origined from
|
|
60
|
+
current_frame = inspect.currentframe()
|
|
61
|
+
root_module_frame = None
|
|
62
|
+
|
|
63
|
+
while current_frame is not None:
|
|
64
|
+
if current_frame.f_code.co_name == "<module>":
|
|
65
|
+
root_module_frame = current_frame
|
|
66
|
+
break
|
|
67
|
+
current_frame = current_frame.f_back
|
|
68
|
+
|
|
69
|
+
return root_module_frame
|
|
40
70
|
|
|
41
71
|
|
|
42
|
-
def on_kernel_start(f: Callable[[], Optional[Callable[[], None]]]):
|
|
43
|
-
|
|
72
|
+
def on_kernel_start(f: Callable[[], Optional[Callable[[], None]]]) -> Callable[[], None]:
|
|
73
|
+
root = _find_root_module_frame()
|
|
74
|
+
path: Optional[Path] = None
|
|
75
|
+
module: Optional[ModuleType] = None
|
|
76
|
+
if root is not None:
|
|
77
|
+
path_str = inspect.getsourcefile(root)
|
|
78
|
+
module = inspect.getmodule(root)
|
|
79
|
+
if path_str is not None:
|
|
80
|
+
path = Path(path_str)
|
|
81
|
+
|
|
82
|
+
def cleanup():
|
|
83
|
+
return _on_kernel_start_callbacks.remove(kce)
|
|
84
|
+
|
|
85
|
+
kce = _on_kernel_callback_entry(f, path, module, cleanup)
|
|
86
|
+
_on_kernel_start_callbacks.append(kce)
|
|
87
|
+
return cleanup
|
|
44
88
|
|
|
45
89
|
|
|
46
90
|
@dataclasses.dataclass
|
|
@@ -74,7 +118,7 @@ class VirtualKernelContext:
|
|
|
74
118
|
|
|
75
119
|
def __post_init__(self):
|
|
76
120
|
with self:
|
|
77
|
-
for f in _on_kernel_start_callbacks:
|
|
121
|
+
for (f, *_) in _on_kernel_start_callbacks:
|
|
78
122
|
cleanup = f()
|
|
79
123
|
if cleanup:
|
|
80
124
|
self.on_close(cleanup)
|
|
@@ -106,6 +150,9 @@ class VirtualKernelContext:
|
|
|
106
150
|
current_context[key] = local.kernel_context_stack.pop()
|
|
107
151
|
|
|
108
152
|
def close(self):
|
|
153
|
+
if self.closed_event.is_set():
|
|
154
|
+
logger.error("Tried to close a kernel context that is already closed: %s", self.id)
|
|
155
|
+
return
|
|
109
156
|
logger.info("Shut down virtual kernel: %s", self.id)
|
|
110
157
|
with self:
|
|
111
158
|
for f in reversed(self._on_close_callbacks):
|
|
@@ -255,12 +302,35 @@ def create_dummy_context():
|
|
|
255
302
|
return kernel_context
|
|
256
303
|
|
|
257
304
|
|
|
305
|
+
if contextvars is not None:
|
|
306
|
+
if typing.TYPE_CHECKING:
|
|
307
|
+
async_context_id = contextvars.ContextVar[str]("async_context_id")
|
|
308
|
+
else:
|
|
309
|
+
async_context_id = contextvars.ContextVar("async_context_id")
|
|
310
|
+
async_context_id.set("default")
|
|
311
|
+
else:
|
|
312
|
+
async_context_id = None
|
|
313
|
+
|
|
314
|
+
|
|
258
315
|
def get_current_thread_key() -> str:
|
|
259
|
-
|
|
260
|
-
|
|
316
|
+
if not solara.server.settings.kernel.threaded:
|
|
317
|
+
if async_context_id is not None:
|
|
318
|
+
try:
|
|
319
|
+
key = async_context_id.get()
|
|
320
|
+
except LookupError:
|
|
321
|
+
raise RuntimeError("no kernel context set")
|
|
322
|
+
else:
|
|
323
|
+
raise RuntimeError("No threading support, and no contextvars support (Python 3.6 is not supported for this)")
|
|
324
|
+
else:
|
|
325
|
+
thread = threading.current_thread()
|
|
326
|
+
key = get_thread_key(thread)
|
|
327
|
+
return key
|
|
261
328
|
|
|
262
329
|
|
|
263
330
|
def get_thread_key(thread: threading.Thread) -> str:
|
|
331
|
+
if not solara.server.settings.kernel.threaded:
|
|
332
|
+
if async_context_id is not None:
|
|
333
|
+
return async_context_id.get()
|
|
264
334
|
thread_key = thread._name + str(thread._ident) # type: ignore
|
|
265
335
|
return thread_key
|
|
266
336
|
|
|
@@ -318,6 +388,7 @@ def initialize_virtual_kernel(session_id: str, kernel_id: str, websocket: websoc
|
|
|
318
388
|
widgets.register_comm_target(kernel)
|
|
319
389
|
appmodule.register_solara_comm_target(kernel)
|
|
320
390
|
with context:
|
|
391
|
+
assert has_current_context()
|
|
321
392
|
assert kernel is Kernel.instance()
|
|
322
393
|
kernel.shell_stream = WebsocketStreamWrapper(websocket, "shell")
|
|
323
394
|
kernel.control_stream = WebsocketStreamWrapper(websocket, "control")
|
solara/server/patch.py
CHANGED
|
@@ -295,7 +295,10 @@ def _WidgetContextAwareThread__bootstrap(self):
|
|
|
295
295
|
# we need to call this manually, because set_context_for_thread
|
|
296
296
|
# uses this, and the original _bootstrap calls it too late for us
|
|
297
297
|
self._set_ident()
|
|
298
|
+
if kernel_context.async_context_id is not None:
|
|
299
|
+
kernel_context.async_context_id.set(self.current_context.id)
|
|
298
300
|
kernel_context.set_context_for_thread(self.current_context, self)
|
|
301
|
+
|
|
299
302
|
shell = self.current_context.kernel.shell
|
|
300
303
|
shell.display_pub.register_hook(shell.display_in_reacton_hook)
|
|
301
304
|
try:
|
solara/server/reload.py
CHANGED
|
@@ -152,13 +152,13 @@ class Reloader:
|
|
|
152
152
|
self._first = False
|
|
153
153
|
|
|
154
154
|
def _on_change(self, name):
|
|
155
|
-
# used for testing
|
|
156
|
-
self.reload_event_next.set()
|
|
157
155
|
# flag that we need to reload all modules next time
|
|
158
156
|
self.requires_reload = True
|
|
159
157
|
# and forward callback
|
|
160
158
|
if self.on_change:
|
|
161
159
|
self.on_change(name)
|
|
160
|
+
# used for testing
|
|
161
|
+
self.reload_event_next.set()
|
|
162
162
|
|
|
163
163
|
def close(self):
|
|
164
164
|
self.watcher.close()
|
solara/server/server.py
CHANGED
|
@@ -13,7 +13,6 @@ import ipyvue
|
|
|
13
13
|
import ipywidgets
|
|
14
14
|
import jinja2
|
|
15
15
|
import requests
|
|
16
|
-
|
|
17
16
|
import solara
|
|
18
17
|
import solara.routing
|
|
19
18
|
import solara.settings
|
|
@@ -35,6 +34,7 @@ ipykernel_major = int(ipykernel.__version__.split(".")[0])
|
|
|
35
34
|
ipywidgets_major = int(ipywidgets.__version__.split(".")[0])
|
|
36
35
|
cache_memory = solara.cache.Memory(max_items=128)
|
|
37
36
|
vue3 = ipyvue.__version__.startswith("3")
|
|
37
|
+
_redirects: Dict[str, str] = {}
|
|
38
38
|
|
|
39
39
|
# first look at the project directory, then the builtin solara directory
|
|
40
40
|
|
|
@@ -302,9 +302,9 @@ def read_root(path: str, root_path: str = "", render_kwargs={}, use_nbextensions
|
|
|
302
302
|
code = content_utf8
|
|
303
303
|
elif embed:
|
|
304
304
|
content_utf8 = content.decode("utf-8")
|
|
305
|
-
code = f
|
|
305
|
+
code = f'<style class="solara-template-css">/*\npath={path}\n*/\n{content_utf8}</style>'
|
|
306
306
|
else:
|
|
307
|
-
code = f'<link rel="stylesheet" type="text/css" href="{url}">'
|
|
307
|
+
code = f'<link rel="stylesheet" type="text/css" href="{url}" class="solara-template-css">'
|
|
308
308
|
return Markup(code)
|
|
309
309
|
|
|
310
310
|
def include_js(path: str, module=False) -> Markup:
|
solara/server/settings.py
CHANGED
solara/server/starlette.py
CHANGED
|
@@ -6,7 +6,7 @@ import os
|
|
|
6
6
|
import sys
|
|
7
7
|
import threading
|
|
8
8
|
import typing
|
|
9
|
-
from typing import Any, Dict, List, Optional, Union, cast
|
|
9
|
+
from typing import Any, Dict, List, Optional, Set, Union, cast
|
|
10
10
|
from uuid import uuid4
|
|
11
11
|
|
|
12
12
|
import anyio
|
|
@@ -40,7 +40,7 @@ from starlette.middleware import Middleware
|
|
|
40
40
|
from starlette.middleware.authentication import AuthenticationMiddleware
|
|
41
41
|
from starlette.middleware.gzip import GZipMiddleware
|
|
42
42
|
from starlette.requests import HTTPConnection, Request
|
|
43
|
-
from starlette.responses import HTMLResponse, JSONResponse, Response
|
|
43
|
+
from starlette.responses import HTMLResponse, JSONResponse, RedirectResponse, Response
|
|
44
44
|
from starlette.routing import Mount, Route, WebSocketRoute
|
|
45
45
|
from starlette.staticfiles import StaticFiles
|
|
46
46
|
from starlette.types import Receive, Scope, Send
|
|
@@ -96,10 +96,14 @@ class WebsocketDebugInfo:
|
|
|
96
96
|
class WebsocketWrapper(websocket.WebsocketWrapper):
|
|
97
97
|
ws: starlette.websockets.WebSocket
|
|
98
98
|
|
|
99
|
-
def __init__(self, ws: starlette.websockets.WebSocket, portal: anyio.from_thread.BlockingPortal) -> None:
|
|
99
|
+
def __init__(self, ws: starlette.websockets.WebSocket, portal: Optional[anyio.from_thread.BlockingPortal]) -> None:
|
|
100
100
|
self.ws = ws
|
|
101
101
|
self.portal = portal
|
|
102
102
|
self.to_send: List[Union[str, bytes]] = []
|
|
103
|
+
# following https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task
|
|
104
|
+
# we store a strong reference
|
|
105
|
+
self.tasks: Set[asyncio.Task] = set()
|
|
106
|
+
self.event_loop = asyncio.get_event_loop()
|
|
103
107
|
if settings.main.experimental_performance:
|
|
104
108
|
self.task = asyncio.ensure_future(self.process_messages_task())
|
|
105
109
|
|
|
@@ -114,28 +118,44 @@ class WebsocketWrapper(websocket.WebsocketWrapper):
|
|
|
114
118
|
await self.ws.send_text(first)
|
|
115
119
|
|
|
116
120
|
def close(self):
|
|
117
|
-
self.portal
|
|
121
|
+
if self.portal is None:
|
|
122
|
+
asyncio.ensure_future(self.ws.close())
|
|
123
|
+
else:
|
|
124
|
+
self.portal.call(self.ws.close) # type: ignore
|
|
118
125
|
|
|
119
126
|
def send_text(self, data: str) -> None:
|
|
120
|
-
if
|
|
121
|
-
self.
|
|
127
|
+
if self.portal is None:
|
|
128
|
+
task = self.event_loop.create_task(self.ws.send_text(data))
|
|
129
|
+
self.tasks.add(task)
|
|
130
|
+
task.add_done_callback(self.tasks.discard)
|
|
122
131
|
else:
|
|
123
|
-
|
|
132
|
+
if settings.main.experimental_performance:
|
|
133
|
+
self.to_send.append(data)
|
|
134
|
+
else:
|
|
135
|
+
self.portal.call(self.ws.send_bytes, data) # type: ignore
|
|
124
136
|
|
|
125
137
|
def send_bytes(self, data: bytes) -> None:
|
|
126
|
-
if
|
|
127
|
-
self.
|
|
138
|
+
if self.portal is None:
|
|
139
|
+
task = self.event_loop.create_task(self.ws.send_bytes(data))
|
|
140
|
+
self.tasks.add(task)
|
|
141
|
+
task.add_done_callback(self.tasks.discard)
|
|
128
142
|
else:
|
|
129
|
-
|
|
143
|
+
if settings.main.experimental_performance:
|
|
144
|
+
self.to_send.append(data)
|
|
145
|
+
else:
|
|
146
|
+
self.portal.call(self.ws.send_bytes, data) # type: ignore
|
|
130
147
|
|
|
131
148
|
async def receive(self):
|
|
132
|
-
if
|
|
133
|
-
|
|
134
|
-
fut = self.portal.start_task_soon(self.ws.receive) # type: ignore
|
|
149
|
+
if self.portal is None:
|
|
150
|
+
message = await asyncio.ensure_future(self.ws.receive())
|
|
135
151
|
else:
|
|
136
|
-
|
|
152
|
+
if hasattr(self.portal, "start_task_soon"):
|
|
153
|
+
# version 3+
|
|
154
|
+
fut = self.portal.start_task_soon(self.ws.receive) # type: ignore
|
|
155
|
+
else:
|
|
156
|
+
fut = self.portal.spawn_task(self.ws.receive) # type: ignore
|
|
137
157
|
|
|
138
|
-
|
|
158
|
+
message = await asyncio.wrap_future(fut)
|
|
139
159
|
if "text" in message:
|
|
140
160
|
return message["text"]
|
|
141
161
|
elif "bytes" in message:
|
|
@@ -237,35 +257,45 @@ async def _kernel_connection(ws: starlette.websockets.WebSocket):
|
|
|
237
257
|
WebsocketDebugInfo.connecting -= 1
|
|
238
258
|
WebsocketDebugInfo.open += 1
|
|
239
259
|
|
|
240
|
-
def
|
|
241
|
-
|
|
260
|
+
async def run(ws_wrapper: WebsocketWrapper):
|
|
261
|
+
if kernel_context.async_context_id is not None:
|
|
262
|
+
kernel_context.async_context_id.set(uuid4().hex)
|
|
263
|
+
assert session_id is not None
|
|
264
|
+
assert kernel_id is not None
|
|
265
|
+
telemetry.connection_open(session_id)
|
|
266
|
+
headers_dict: Dict[str, List[str]] = {}
|
|
267
|
+
for k, v in ws.headers.items():
|
|
268
|
+
if k not in headers_dict.keys():
|
|
269
|
+
headers_dict[k] = [v]
|
|
270
|
+
else:
|
|
271
|
+
headers_dict[k].append(v)
|
|
272
|
+
await server.app_loop(ws_wrapper, ws.cookies, headers_dict, session_id, kernel_id, page_id, user)
|
|
273
|
+
|
|
274
|
+
def websocket_thread_runner(ws_wrapper: WebsocketWrapper, portal: anyio.from_thread.BlockingPortal):
|
|
275
|
+
async def run_wrapper():
|
|
242
276
|
try:
|
|
243
|
-
|
|
244
|
-
assert kernel_id is not None
|
|
245
|
-
telemetry.connection_open(session_id)
|
|
246
|
-
headers_dict: Dict[str, List[str]] = {}
|
|
247
|
-
for k, v in ws.headers.items():
|
|
248
|
-
if k not in headers_dict.keys():
|
|
249
|
-
headers_dict[k] = [v]
|
|
250
|
-
else:
|
|
251
|
-
headers_dict[k].append(v)
|
|
252
|
-
await server.app_loop(ws_wrapper, ws.cookies, headers_dict, session_id, kernel_id, page_id, user)
|
|
277
|
+
await run(ws_wrapper)
|
|
253
278
|
except: # noqa
|
|
254
|
-
|
|
279
|
+
if portal is not None:
|
|
280
|
+
await portal.stop(cancel_remaining=True)
|
|
255
281
|
raise
|
|
256
282
|
finally:
|
|
257
283
|
telemetry.connection_close(session_id)
|
|
258
284
|
|
|
259
285
|
# sometimes throws: RuntimeError: Already running asyncio in this thread
|
|
260
|
-
anyio.run(
|
|
286
|
+
anyio.run(run_wrapper) # type: ignore
|
|
261
287
|
|
|
262
288
|
# this portal allows us to sync call the websocket calls from this current event loop we are in
|
|
263
289
|
# each websocket however, is handled from a separate thread
|
|
264
290
|
try:
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
291
|
+
if settings.kernel.threaded:
|
|
292
|
+
async with anyio.from_thread.BlockingPortal() as portal:
|
|
293
|
+
ws_wrapper = WebsocketWrapper(ws, portal)
|
|
294
|
+
thread_return = anyio.to_thread.run_sync(websocket_thread_runner, ws_wrapper, portal, limiter=limiter) # type: ignore
|
|
295
|
+
await thread_return
|
|
296
|
+
else:
|
|
297
|
+
ws_wrapper = WebsocketWrapper(ws, None)
|
|
298
|
+
await run(ws_wrapper)
|
|
269
299
|
finally:
|
|
270
300
|
if settings.main.experimental_performance:
|
|
271
301
|
try:
|
|
@@ -315,6 +345,9 @@ async def root(request: Request, fullpath: str = ""):
|
|
|
315
345
|
request_path = request.url.path
|
|
316
346
|
if request_path.startswith(root_path):
|
|
317
347
|
request_path = request_path[len(root_path) :]
|
|
348
|
+
if request_path in server._redirects.keys():
|
|
349
|
+
return RedirectResponse(server._redirects[request_path])
|
|
350
|
+
|
|
318
351
|
content = server.read_root(request_path, root_path)
|
|
319
352
|
if content is None:
|
|
320
353
|
if settings.oauth.private and not request.user.is_authenticated:
|
|
@@ -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.30.1-py2.py3-none-any.whl", keep_going=True)
|
|
123
123
|
import solara
|
|
124
124
|
|
|
125
125
|
el = solara.Warning("lala")
|
|
@@ -11,13 +11,11 @@
|
|
|
11
11
|
|
|
12
12
|
{% block header %}
|
|
13
13
|
|
|
14
|
-
|
|
15
14
|
{{ pre_rendered_css | safe }}
|
|
16
|
-
|
|
17
15
|
{% if vue3 == True %}
|
|
18
|
-
<link href="{{cdn}}/@widgetti/solara-vuetify3-app@5.0.1/dist/main{{ipywidget_major_version}}.css" rel="stylesheet"></link>
|
|
16
|
+
<link href="{{cdn}}/@widgetti/solara-vuetify3-app@5.0.1/dist/main{{ipywidget_major_version}}.css" rel="stylesheet" class="solara-template-css"></link>
|
|
19
17
|
{% else %}
|
|
20
|
-
<link href="{{cdn}}/@widgetti/solara-vuetify-app@10.0.1/dist/main{{ipywidget_major_version}}.css" rel="stylesheet"></link>
|
|
18
|
+
<link href="{{cdn}}/@widgetti/solara-vuetify-app@10.0.1/dist/main{{ipywidget_major_version}}.css" rel="stylesheet" class="solara-template-css"></link>
|
|
21
19
|
{% endif %}
|
|
22
20
|
|
|
23
21
|
|
|
@@ -27,8 +25,7 @@
|
|
|
27
25
|
{{ resources.include_css("/static/highlight.css") }}
|
|
28
26
|
{{ resources.include_css("/static/highlight-dark.css") }}
|
|
29
27
|
{{ resources.include_css("/static/assets/style.css") }}
|
|
30
|
-
<style id="jupyter-theme-css">
|
|
31
|
-
{{ resources.include_css("/static/assets/theme-"~theme.variant~".css") }}
|
|
28
|
+
<style id="jupyter-theme-css" class="solara-template-css">
|
|
32
29
|
</style>
|
|
33
30
|
{{ resources.include_css("/static/assets/custom.css") }}
|
|
34
31
|
|
|
@@ -38,9 +35,24 @@
|
|
|
38
35
|
"kernelId": "1234"
|
|
39
36
|
}
|
|
40
37
|
</script>
|
|
41
|
-
<style>
|
|
38
|
+
<style class="solara-template-css">
|
|
42
39
|
{% include "loader-"~theme.loader~".css" %}
|
|
43
40
|
</style>
|
|
41
|
+
<!-- Include Vuetify background colours so static html from SSG renders the right general colour theme
|
|
42
|
+
before first render. We remove these after Vue takes over rendering to avoid collisions -->
|
|
43
|
+
<style id="pre-render-theme">
|
|
44
|
+
.theme--light .v-sheet {
|
|
45
|
+
background-color: #fff;
|
|
46
|
+
border-color: #fff;
|
|
47
|
+
color: rgba(0,0,0,.87)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.theme--dark .v-sheet {
|
|
51
|
+
background-color: #1e1e1e;
|
|
52
|
+
border-color: #1e1e1e;
|
|
53
|
+
color: #fff
|
|
54
|
+
}
|
|
55
|
+
</style>
|
|
44
56
|
{% endblock header %}
|
|
45
57
|
</head>
|
|
46
58
|
{% raw -%}
|
|
@@ -175,6 +187,38 @@
|
|
|
175
187
|
{# next div is used in ssg code to see if vue took over rendering #}
|
|
176
188
|
<div id="pre-rendered-html-present" style="display: none"></div>
|
|
177
189
|
</div>
|
|
190
|
+
<script>
|
|
191
|
+
var theme = {{ theme | tojson | safe }}
|
|
192
|
+
function getThemeVariant() {
|
|
193
|
+
if (localStorage.getItem(':solara:theme.variant')) {
|
|
194
|
+
return JSON.parse(localStorage.getItem(':solara:theme.variant'))
|
|
195
|
+
}
|
|
196
|
+
return theme.variant;
|
|
197
|
+
}
|
|
198
|
+
if (localStorage.getItem(':solara:theme.variant')) {
|
|
199
|
+
theme.variant = JSON.parse(localStorage.getItem(':solara:theme.variant'))
|
|
200
|
+
}
|
|
201
|
+
function prefersDarkScheme() {
|
|
202
|
+
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
203
|
+
}
|
|
204
|
+
function inDarkMode() {
|
|
205
|
+
if (getThemeVariant() == 'auto') {
|
|
206
|
+
return prefersDarkScheme();
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
return getThemeVariant() == 'dark';
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Init theme
|
|
213
|
+
let appContainer = document.getElementById('app');
|
|
214
|
+
if (inDarkMode()) {
|
|
215
|
+
appContainer.classList.remove('theme--light');
|
|
216
|
+
appContainer.classList.add('theme--dark');
|
|
217
|
+
} else {
|
|
218
|
+
appContainer.classList.remove('theme--dark');
|
|
219
|
+
appContainer.classList.add('theme--light');
|
|
220
|
+
}
|
|
221
|
+
</script>
|
|
178
222
|
{% block after_pre_rendered_html %}{% endblock %}
|
|
179
223
|
{% if vue3 == True %}
|
|
180
224
|
<link href="{{cdn}}/@widgetti/solara-vuetify3-app@5.0.1/dist/fonts.css" rel="stylesheet"></link>
|
|
@@ -194,6 +238,12 @@
|
|
|
194
238
|
<script>
|
|
195
239
|
solara.rootPath = {{ root_path | tojson | safe}};
|
|
196
240
|
console.log("rootPath", solara.rootPath);
|
|
241
|
+
|
|
242
|
+
async function changeThemeCSS(theme) {
|
|
243
|
+
let css = await fetch(`${solara.rootPath}/static/assets/theme-${theme}.css`).then(r => r.text());
|
|
244
|
+
document.getElementById('jupyter-theme-css').innerHTML = css;
|
|
245
|
+
}
|
|
246
|
+
changeThemeCSS(inDarkMode() ? 'dark' : 'light');
|
|
197
247
|
</script>
|
|
198
248
|
|
|
199
249
|
{{ resources.include_js("/static/assets/custom.js") }}
|
|
@@ -208,27 +258,6 @@
|
|
|
208
258
|
solara.production = {{ production | tojson | safe }};
|
|
209
259
|
const themeVariants = ['light', 'dark', 'auto']
|
|
210
260
|
solara.preRendered = {{ pre_rendered_html | safe | length | tojson }} > 0
|
|
211
|
-
var theme = {{ theme | tojson | safe }}
|
|
212
|
-
function getThemeVariant() {
|
|
213
|
-
if (localStorage.getItem(':solara:theme.variant')) {
|
|
214
|
-
return JSON.parse(localStorage.getItem(':solara:theme.variant'))
|
|
215
|
-
}
|
|
216
|
-
return theme.variant;
|
|
217
|
-
}
|
|
218
|
-
if (localStorage.getItem(':solara:theme.variant')) {
|
|
219
|
-
theme.variant = JSON.parse(localStorage.getItem(':solara:theme.variant'))
|
|
220
|
-
}
|
|
221
|
-
function prefersDarkScheme() {
|
|
222
|
-
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
223
|
-
}
|
|
224
|
-
function inDarkMode() {
|
|
225
|
-
if (getThemeVariant() == 'auto') {
|
|
226
|
-
return prefersDarkScheme();
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
229
|
-
return getThemeVariant() == 'dark';
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
261
|
</script>
|
|
233
262
|
|
|
234
263
|
<script>
|
|
@@ -256,11 +285,7 @@
|
|
|
256
285
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
|
257
286
|
this.$vuetify.theme.dark = inDarkMode();
|
|
258
287
|
});
|
|
259
|
-
|
|
260
|
-
this.changeThemeCSS('dark');
|
|
261
|
-
} else {
|
|
262
|
-
this.changeThemeCSS('light');
|
|
263
|
-
}
|
|
288
|
+
document.getElementById('pre-render-theme').remove();
|
|
264
289
|
},
|
|
265
290
|
methods: {
|
|
266
291
|
stateReset() {
|
|
@@ -275,10 +300,6 @@
|
|
|
275
300
|
reload() {
|
|
276
301
|
location.reload();
|
|
277
302
|
},
|
|
278
|
-
async changeThemeCSS(theme) {
|
|
279
|
-
let css = await fetch(`/static/assets/theme-${theme}.css`).then(r => r.text());
|
|
280
|
-
document.getElementById('jupyter-theme-css').innerHTML = css;
|
|
281
|
-
}
|
|
282
303
|
},
|
|
283
304
|
watch: {
|
|
284
305
|
kernelBusy: function (value) {
|
|
@@ -325,15 +346,14 @@
|
|
|
325
346
|
}
|
|
326
347
|
},
|
|
327
348
|
'$vuetify.theme.dark': function (value) {
|
|
328
|
-
let app = document.getElementById('app');
|
|
329
349
|
if ( value ) {
|
|
330
350
|
this.changeThemeCSS('dark');
|
|
331
|
-
|
|
332
|
-
|
|
351
|
+
appContainer.classList.remove('theme--light');
|
|
352
|
+
appContainer.classList.add('theme--dark');
|
|
333
353
|
} else {
|
|
334
354
|
this.changeThemeCSS('light');
|
|
335
|
-
|
|
336
|
-
|
|
355
|
+
appContainer.classList.remove('theme--dark');
|
|
356
|
+
appContainer.classList.add('theme--light');
|
|
337
357
|
}
|
|
338
358
|
}
|
|
339
359
|
},
|
solara/tasks.py
CHANGED
|
@@ -32,12 +32,7 @@ P = typing_extensions.ParamSpec("P")
|
|
|
32
32
|
|
|
33
33
|
logger = logging.getLogger("solara.task")
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
threading.Thread(target=lambda: None).start()
|
|
37
|
-
has_threads = True
|
|
38
|
-
except RuntimeError:
|
|
39
|
-
has_threads = False
|
|
40
|
-
has_threads
|
|
35
|
+
has_threads = solara.util.has_threads
|
|
41
36
|
|
|
42
37
|
|
|
43
38
|
class TaskState(Enum):
|
solara/util.py
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import contextlib
|
|
3
|
+
import gzip
|
|
3
4
|
import hashlib
|
|
5
|
+
import json
|
|
4
6
|
import os
|
|
5
7
|
import sys
|
|
6
8
|
import threading
|
|
7
9
|
from collections import abc
|
|
8
10
|
from pathlib import Path
|
|
9
|
-
from typing import TYPE_CHECKING, Dict, List, Tuple, Union
|
|
11
|
+
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
|
|
12
|
+
from urllib.parse import urlencode
|
|
10
13
|
|
|
11
14
|
if TYPE_CHECKING:
|
|
12
15
|
import numpy as np
|
|
@@ -21,6 +24,12 @@ SOLARA_ALLOW_OTHER_TRACER = os.environ.get("SOLARA_ALLOW_OTHER_TRACER", False) i
|
|
|
21
24
|
ipyvuetify_major_version = int(ipyvuetify.__version__.split(".")[0])
|
|
22
25
|
ipywidgets_major = int(ipywidgets.__version__.split(".")[0])
|
|
23
26
|
|
|
27
|
+
try:
|
|
28
|
+
threading.Thread(target=lambda: None).start()
|
|
29
|
+
has_threads = True
|
|
30
|
+
except RuntimeError:
|
|
31
|
+
has_threads = False
|
|
32
|
+
|
|
24
33
|
|
|
25
34
|
def github_url(file):
|
|
26
35
|
rel_path = os.path.relpath(file, Path(solara.__file__).parent.parent)
|
|
@@ -28,6 +37,19 @@ def github_url(file):
|
|
|
28
37
|
return github_url
|
|
29
38
|
|
|
30
39
|
|
|
40
|
+
def pycafe_url(*, path: Path, requirements: Optional[List[str]] = None):
|
|
41
|
+
json_object = {"code": path.read_text("utf8")}
|
|
42
|
+
if requirements:
|
|
43
|
+
json_object["requirements"] = "\n".join(requirements)
|
|
44
|
+
json_text = json.dumps(json_object)
|
|
45
|
+
# gzip -> base64
|
|
46
|
+
compressed_json_text = gzip.compress(json_text.encode("utf8"))
|
|
47
|
+
base64_text = base64.b64encode(compressed_json_text).decode("utf8")
|
|
48
|
+
query = urlencode({"c": base64_text})
|
|
49
|
+
base_url = "https://app.py.cafe"
|
|
50
|
+
return f"{base_url}/snippet/solara?{query}"
|
|
51
|
+
|
|
52
|
+
|
|
31
53
|
def github_edit_url(file):
|
|
32
54
|
# e.g. https://github.com/widgetti/solara/edit/master/solara/__init__.py
|
|
33
55
|
rel_path = os.path.relpath(file, Path(solara.__file__).parent.parent)
|