|
12 | 12 | # See the License for the specific language governing permissions and |
13 | 13 | # limitations under the License. |
14 | 14 |
|
| 15 | +import importlib.metadata |
15 | 16 | import json |
16 | 17 | import os |
| 18 | +import re |
17 | 19 | from typing import TYPE_CHECKING |
18 | | -import streamlit.components.v1 as components |
19 | | -import importlib.metadata |
| 20 | + |
20 | 21 | import bokeh |
| 22 | +import streamlit as st |
21 | 23 | from bokeh.embed import json_item |
22 | 24 |
|
| 25 | +if TYPE_CHECKING: |
| 26 | + from bokeh.plotting.figure import Figure |
| 27 | + |
| 28 | + |
23 | 29 | # Create a _RELEASE constant. We'll set this to False while we're developing |
24 | 30 | # the component, and True when we're ready to package and distribute it. |
25 | 31 | # (This is, of course, optional - there are innumerable ways to manage your |
26 | 32 | # release process.) |
27 | 33 | _DEV = os.environ.get("DEV", False) |
28 | 34 | _RELEASE = not _DEV |
29 | 35 |
|
30 | | -# Declare a Streamlit component. `declare_component` returns a function |
31 | | -# that is used to create instances of the component. We're naming this |
32 | | -# function "_component_func", with an underscore prefix, because we don't want |
33 | | -# to expose it directly to users. Instead, we will create a custom wrapper |
34 | | -# function, below, that will serve as our component's public API. |
35 | | - |
36 | | -# It's worth noting that this call to `declare_component` is the |
37 | | -# *only thing* you need to do to create the binding between Streamlit and |
38 | | -# your component frontend. Everything else we do in this file is simply a |
39 | | -# best practice. |
40 | | - |
41 | | -if not _RELEASE: |
42 | | - _component_func = components.declare_component( |
43 | | - # We give the component a simple, descriptive name ("streamlit_bokeh" |
44 | | - # does not fit this bill, so please choose something better for your |
45 | | - # own component :) |
46 | | - "streamlit_bokeh", |
47 | | - # Pass `url` here to tell Streamlit that the component will be served |
48 | | - # by the local dev server that you run via `npm run start`. |
49 | | - # (This is useful while your component is in development.) |
50 | | - url="http://localhost:3001", |
| 36 | + |
| 37 | +def _version_ge(a: str, b: str) -> bool: |
| 38 | + """ |
| 39 | + Return True if version string a is greater than or equal to b. |
| 40 | +
|
| 41 | + The comparison extracts up to three numeric components from each version |
| 42 | + string (major, minor, patch) and compares them as integer tuples. |
| 43 | + Non-numeric suffixes (for example, 'rc1', 'dev') are ignored. |
| 44 | +
|
| 45 | + Parameters |
| 46 | + ---------- |
| 47 | + a : str |
| 48 | + The left-hand version string. |
| 49 | + b : str |
| 50 | + The right-hand version string to compare against. |
| 51 | +
|
| 52 | + Returns |
| 53 | + ------- |
| 54 | + bool |
| 55 | + True if a >= b, otherwise False. |
| 56 | + """ |
| 57 | + |
| 58 | + def parse(v: str) -> tuple[int, int, int]: |
| 59 | + nums = [int(x) for x in re.findall(r"\d+", v)[:3]] |
| 60 | + while len(nums) < 3: |
| 61 | + nums.append(0) |
| 62 | + return nums[0], nums[1], nums[2] |
| 63 | + |
| 64 | + return parse(a) >= parse(b) |
| 65 | + |
| 66 | + |
| 67 | +_STREAMLIT_VERSION = importlib.metadata.version("streamlit") |
| 68 | + |
| 69 | +# If streamlit version is >= 1.51.0 use Custom Component v2 API, otherwise use |
| 70 | +# Custom Component v1 API |
| 71 | +# _IS_USING_CCV2 = _version_ge(_STREAMLIT_VERSION, "1.51.0") |
| 72 | +# Temporarily setting this to False, will be updated in next PR. |
| 73 | +_IS_USING_CCV2 = False |
| 74 | + |
| 75 | +# Version-gated component registration |
| 76 | +if _IS_USING_CCV2: |
| 77 | + _component_func = st.components.v2.component( |
| 78 | + "streamlit-bokeh.streamlit_bokeh", |
| 79 | + js="v2/index-*.mjs", |
| 80 | + html="<div class='stBokehContainer'></div>", |
51 | 81 | ) |
52 | 82 | else: |
53 | | - # When we're distributing a production version of the component, we'll |
54 | | - # replace the `url` param with `path`, and point it to the component's |
55 | | - # build directory: |
56 | | - parent_dir = os.path.dirname(os.path.abspath(__file__)) |
57 | | - build_dir = os.path.join(parent_dir, "frontend/build") |
58 | | - _component_func = components.declare_component("streamlit_bokeh", path=build_dir) |
| 83 | + if not _RELEASE: |
| 84 | + _component_func = st.components.v1.declare_component( |
| 85 | + "streamlit_bokeh", |
| 86 | + url="http://localhost:3001", |
| 87 | + ) |
| 88 | + else: |
| 89 | + parent_dir = os.path.dirname(os.path.abspath(__file__)) |
| 90 | + build_dir = os.path.join(parent_dir, "frontend/build") |
| 91 | + _component_func = st.components.v1.declare_component( |
| 92 | + "streamlit_bokeh", path=build_dir |
| 93 | + ) |
59 | 94 |
|
60 | | -if TYPE_CHECKING: |
61 | | - from bokeh.plotting.figure import Figure |
62 | 95 |
|
63 | 96 | __version__ = importlib.metadata.version("streamlit_bokeh") |
64 | 97 | REQUIRED_BOKEH_VERSION = "3.8.0" |
@@ -112,14 +145,28 @@ def streamlit_bokeh( |
112 | 145 | f"{REQUIRED_BOKEH_VERSION}` to install the correct version." |
113 | 146 | ) |
114 | 147 |
|
115 | | - # Call through to our private component function. Arguments we pass here |
116 | | - # will be sent to the frontend, where they'll be available in an "args" |
117 | | - # dictionary. |
118 | | - _component_func( |
119 | | - figure=json.dumps(json_item(figure)), |
120 | | - use_container_width=use_container_width, |
121 | | - bokeh_theme=theme, |
122 | | - key=key, |
123 | | - ) |
| 148 | + if _IS_USING_CCV2: |
| 149 | + # Call through to our private component function. |
| 150 | + _component_func( |
| 151 | + key=key, |
| 152 | + data={ |
| 153 | + "figure": json.dumps(json_item(figure)), |
| 154 | + "bokeh_theme": theme, |
| 155 | + "use_container_width": use_container_width, |
| 156 | + }, |
| 157 | + isolate_styles=False, |
| 158 | + ) |
| 159 | + |
| 160 | + return None |
| 161 | + else: |
| 162 | + # Call through to our private component function. Arguments we pass here |
| 163 | + # will be sent to the frontend, where they'll be available in an "args" |
| 164 | + # dictionary. |
| 165 | + _component_func( |
| 166 | + figure=json.dumps(json_item(figure)), |
| 167 | + use_container_width=use_container_width, |
| 168 | + bokeh_theme=theme, |
| 169 | + key=key, |
| 170 | + ) |
124 | 171 |
|
125 | | - return None |
| 172 | + return None |
0 commit comments