holoviz-mcp 0.0.1a0__py3-none-any.whl → 0.0.1a2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of holoviz-mcp might be problematic. Click here for more details.

Files changed (37) hide show
  1. holoviz_mcp/__init__.py +18 -0
  2. holoviz_mcp/apps/__init__.py +1 -0
  3. holoviz_mcp/apps/configuration_viewer.py +116 -0
  4. holoviz_mcp/apps/search.py +314 -0
  5. holoviz_mcp/config/__init__.py +31 -0
  6. holoviz_mcp/config/config.yaml +167 -0
  7. holoviz_mcp/config/loader.py +308 -0
  8. holoviz_mcp/config/models.py +216 -0
  9. holoviz_mcp/config/resources/best-practices/hvplot.md +62 -0
  10. holoviz_mcp/config/resources/best-practices/panel-material-ui.md +318 -0
  11. holoviz_mcp/config/resources/best-practices/panel.md +294 -0
  12. holoviz_mcp/config/schema.json +203 -0
  13. holoviz_mcp/docs_mcp/__init__.py +1 -0
  14. holoviz_mcp/docs_mcp/data.py +963 -0
  15. holoviz_mcp/docs_mcp/models.py +21 -0
  16. holoviz_mcp/docs_mcp/pages_design.md +407 -0
  17. holoviz_mcp/docs_mcp/server.py +220 -0
  18. holoviz_mcp/hvplot_mcp/__init__.py +1 -0
  19. holoviz_mcp/hvplot_mcp/server.py +152 -0
  20. holoviz_mcp/panel_mcp/__init__.py +17 -0
  21. holoviz_mcp/panel_mcp/data.py +316 -0
  22. holoviz_mcp/panel_mcp/models.py +124 -0
  23. holoviz_mcp/panel_mcp/server.py +650 -0
  24. holoviz_mcp/py.typed +0 -0
  25. holoviz_mcp/serve.py +34 -0
  26. holoviz_mcp/server.py +77 -0
  27. holoviz_mcp/shared/__init__.py +1 -0
  28. holoviz_mcp/shared/extract_tools.py +74 -0
  29. holoviz_mcp-0.0.1a2.dist-info/METADATA +641 -0
  30. holoviz_mcp-0.0.1a2.dist-info/RECORD +33 -0
  31. {holoviz_mcp-0.0.1a0.dist-info → holoviz_mcp-0.0.1a2.dist-info}/WHEEL +1 -2
  32. holoviz_mcp-0.0.1a2.dist-info/entry_points.txt +4 -0
  33. holoviz_mcp-0.0.1a2.dist-info/licenses/LICENSE.txt +30 -0
  34. holoviz_mcp-0.0.1a0.dist-info/METADATA +0 -6
  35. holoviz_mcp-0.0.1a0.dist-info/RECORD +0 -5
  36. holoviz_mcp-0.0.1a0.dist-info/top_level.txt +0 -1
  37. main.py +0 -6
@@ -0,0 +1,318 @@
1
+ # Panel Material UI
2
+
3
+ This guide provides best practices for using Panel Material UI. Optimized for LLMs.
4
+
5
+ Please develop code, tests and documentation as an **expert Panel analytics app developer** would do when working with a **short time to market**.
6
+
7
+ **For general Panel best practices, see the Panel best practices guide. This guide focuses on Panel Material UI-specific patterns. This guide takes precedence over the Panel Best Practices guide.**
8
+
9
+ ## Best Practice Hello World App
10
+
11
+ Let's describe our best practices via a basic Hello World App:
12
+
13
+ ```python
14
+ # DO import panel as pn
15
+ import panel as pn
16
+ # DO import panel_material_ui as pmui
17
+ import panel_material_ui as pmui
18
+ import param
19
+
20
+ # DO run pn.extension
21
+ # DO remember to add any imports needed by panes, e.g. pn.extension("tabulator", "plotly", ...)
22
+ # DON'T add "bokeh" as an extension. It is not needed.
23
+ # Do use throttled=True when using slider unless you have a specific reason not to
24
+ pn.extension(throttled=True)
25
+
26
+ # DO organize functions to extract data separately as your app grows
27
+ # DO use caching to speed up the app, e.g. for expensive data loading or processing that would return the same result given same input arguments.
28
+ # DO add a ttl (time to live argument) for expensive data loading that changes over time
29
+ @pn.cache(max_items=3)
30
+ def extract(n=5):
31
+ return "Hello World" + "⭐" * n
32
+
33
+ text = extract()
34
+ text_len = len(text)
35
+
36
+ # DO organize functions to transform data separately as your app grows. Eventually in a separate transform.py file
37
+ # DO add caching to speed up expensive data transformations
38
+ def transform(data: str, count: int=5)->str:
39
+ """
40
+ Transforms the input data by truncating it to the specified count of characters.
41
+ """
42
+ count = min(count, len(data))
43
+ return data[:count]
44
+
45
+ # DO organize functions to create plots separately as your app grows. Eventually in a separate plots.py file.
46
+ # DO organize custom components and views separately as your app grows. Eventually in separate components.py or views.py file(s).
47
+
48
+ # DO use param.Parameterized, pn.viewable.Viewer or similar approach to create new components and apps with state and reactivity
49
+ class HelloWorld(pn.viewable.Viewer):
50
+ """
51
+ A simple Panel app that displays a "Hello World" message with a slider to control the length of the message.
52
+ """
53
+ # DO define parameters to hold state and drive the reactivity
54
+ characters = param.Integer(default=text_len, bounds=(0, text_len), doc="Number of characters to display")
55
+
56
+ def __init__(self, **params):
57
+ super().__init__(**params)
58
+
59
+ # DO use sizing_mode="stretch_width" for components unless "fixed" or other sizing_mode is specifically needed
60
+ with pn.config.set(sizing_mode="stretch_width"):
61
+ # DO create widgets using `.from_param` method
62
+ self._characters_input = pmui.IntSlider.from_param(self.param.characters, margin=(10,20))
63
+ # DO Collect input widgets into horizontal, columnar layout unless other layout is specifically needed
64
+ self._inputs = pmui.Column(self._characters_input, max_width=300)
65
+ # DO collect output components into some layout like Column, Row, FlexBox or Grid depending on use case
66
+ self._outputs = pmui.Column(self.model)
67
+ self._panel = pmui.Row(self._inputs, self._outputs)
68
+
69
+
70
+ # DO use caching to speed up bound methods that are expensive to compute or load data and return the same result for a given state of the class.
71
+ @pn.cache
72
+ # DO prefer .depends over .bind over .rx for reactivity methods on Parameterized classes as it can be typed and documented
73
+ # DON'T use `watch=True` or `.watch` methods to update UI. Only for updating overall app or component state.
74
+ # DO use `watch=True` or `.watch` for triggering side effect like saving file or sending email.
75
+ @param.depends("characters")
76
+ def model(self):
77
+ """
78
+ Returns the "Hello World" message truncated to the specified number of characters.
79
+ """
80
+ return transform(text, self.characters)
81
+
82
+ # DO provide a method for displaying the component in a notebook setting, i.e. without using a Template or Page element
83
+ def __panel__(self):
84
+ return self._panel
85
+
86
+ # DO provide a method to create a .servable app
87
+ @classmethod
88
+ def create_app(cls, **params):
89
+ """
90
+ Create the Panel app with the interactive model and slider.
91
+ """
92
+ instance = cls(**params)
93
+ # DO use the `Page` to layout the served app unless otherwise specified
94
+ return pmui.Page(
95
+ # DO provide a title for the app
96
+ title="Hello World App",
97
+ # DO provide optional image, optional app description, optional navigation menu, input widgets, optional documentation and optional links in the sidebar
98
+ # DO provide as list of components or a list of single horizontal layout like Column as the sidebar by default is 300 px wide
99
+ sidebar=list(instance._inputs),
100
+ # DO provide a list of layouts and output components in the main area of the app.
101
+ # DO use Grid or FlexBox layouts for complex dashboard layouts instead of combination Rows and Columns.
102
+ main=list(instance._outputs),
103
+ )
104
+
105
+ # DO provide a method to serve the app with `python`
106
+ if __name__ == "__main__":
107
+ # DO run with `python path_to_this_file.py`
108
+ HelloWorld.create_app().show(port=5007, autoreload=True, open=True)
109
+ # DO provide a method to serve the app with `panel serve`
110
+ elif pn.state.served:
111
+ # DO run with `panel serve path_to_this_file.py --port 5007 --dev` add `--show` to open the app in a browser
112
+ HelloWorld.create_app().servable() # DO mark the element(s) to serve with .servable()
113
+ ```
114
+
115
+ DO always create test in separate test files and DO run test via pytest:
116
+
117
+
118
+ ```python
119
+ import ...
120
+
121
+ # DO put tests into separate test file(s)
122
+ # DO test the reactivity of each parameter, function, method, component or app.
123
+ # DO run pytest when the code is changed. DON'T create non-pytest scripts or files to test the code.
124
+ def test_characters_reactivity():
125
+ """
126
+ Always test that the reactivity works as expected.
127
+ Put tests in a separate test file.
128
+ """
129
+ # Test to be added in separate test file
130
+ hello_world = HelloWorld()
131
+ assert hello_world.model() == text[:hello_world.characters] # DO test the default values of bound methods
132
+ hello_world.characters = 5
133
+ assert hello_world.model() == text[:5] # DO test the reactivity of bound methods when parameters change
134
+ hello_world.characters = 3
135
+ assert hello_world.model() == text[:3]
136
+ ```
137
+
138
+ ## Panel Material UI Guidelines
139
+
140
+ ### General Guidelines
141
+
142
+ - DO use the new parameter names (e.g. `label`, `color`) instead of legacy aliases (e.g. `name`, `button_type`) for pmui components
143
+ - DO use `sizing_mode` parameter over `sx` css styling parameter
144
+ - DO use Material UI `sx` parameter for all css styling over `styles` and `stylesheets`
145
+ - DO use panel-material-ui components instead of panel components for projects already using panel-material-ui and for new projects
146
+
147
+ ## Component Guidelines
148
+
149
+ ### Page
150
+
151
+ - DO provide the title to the `Page.title` argument. DON'T provide it in the `Page.main` area.
152
+ - DO provide an optional image, description, navigation menu to the `Page.sidebar` argument. Normally DON't put them in the `header` or `main` areas.
153
+ - DO provide the input widgets as children to the `Page.sidebar` argument
154
+ - DO not add advanced or high components to the `Page.header` as it is only 100px high by default. Normally only buttons, indicators, text and navigation links go into the header.
155
+ - DON'T include `ThemeToggle` or other widgets to toggle the theme when using the `Page`. A `ThemeToggle` is already built in.
156
+ - DO Add a little bit of `margin=10` to the outer layout component(s) in the `main` area. To make them stand out from the `sidebar` components: `Grid(..., container=True, margin=15)`.
157
+
158
+ DO provide lists of children to the `Page.sidebar`, `Page.main` or `Page.header` arguments:
159
+
160
+ ```python
161
+ pmui.Page(
162
+ header=[component1, component2], # This is correct
163
+ sidebar=[component3, component4], # This is correct
164
+ main=[a_list_like_layout, a_grid], # This is correct
165
+ )
166
+ ```
167
+
168
+ DON'T provide non-lists as children to the `Page.sidebar`, `Page.main` or `Page.header` arguments:
169
+
170
+ ```python
171
+ pmui.Page(
172
+ header=component1, # This is incorrect
173
+ sidebar=list(a_list_like_layout), # This is incorrect
174
+ main=list(a_grid), # This is incorrect
175
+ )
176
+ ```
177
+
178
+ #### Linking Dashboard Theme with Page Theme
179
+
180
+ DO synchronize component themes with Page theme:
181
+
182
+ ```python
183
+ ...
184
+
185
+ dark_theme = param.Boolean(
186
+ doc="""True if the theme is dark""",
187
+ # To enable providing parameters and bound function references
188
+ allow_refs=True
189
+ )
190
+
191
+ @classmethod
192
+ def create_app(cls, **params):
193
+ """Create app with synchronized theming."""
194
+ component = cls(**params)
195
+
196
+ page = pmui.Page(
197
+ ...,
198
+ dark_theme=component.dark_theme, # Pass theme to Page
199
+ )
200
+
201
+ # Synchronize Page theme to component theme
202
+ component.dark_theme = page.param.dark_theme
203
+ return page
204
+ ```
205
+
206
+ ### Grid
207
+
208
+ - DO set `spacing=2` or higher to separate sub components in the grid.
209
+
210
+ ### Column/ Row
211
+
212
+ - DO use `size` parameter instead of `xs`, `sm` or `md` parameters - they do not exist.
213
+ - DO use `sx` to set `spacing` instead of setting `spacing` directly. It does not exist.
214
+
215
+ ### List like layouts
216
+
217
+ For list-like layouts like `Column` and `Row` DO provide children as positional arguments:
218
+
219
+ ```python
220
+ pmui.Row(child1, child2, child3) # DO
221
+ ```
222
+
223
+ DON'T provide them as separate arguments:
224
+
225
+ ```python
226
+ pmui.Row([child1, child2, child3,]) # DON'T
227
+ ```
228
+
229
+ ### Switch
230
+
231
+ - Do add `margin=(10, 20)` when displaying in the sidebar.
232
+
233
+ ### Sliders
234
+
235
+ - DO add a little bit of margin left and right when displaying in the sidebar.
236
+
237
+ ### Cards
238
+
239
+ - DO use the `Paper` component over the `Card` unless you need the `Card`s extra features.
240
+ - DO set `collapsible=False` unless collapsible is needed.
241
+
242
+ ### Tabulator
243
+
244
+ - DO use "materialize" `theme` instead of "material". The latter does not exist.
245
+
246
+ ### Non-Existing Components
247
+
248
+ - Do use `Column` instead of `Box`. The `Box` component does not exist.
249
+
250
+ ## Material UI Examples
251
+
252
+ ### Standalone Icons
253
+
254
+ DO use `Typography` to make standalone icons without interactivity instead of `IconButton`:
255
+
256
+ ```python
257
+ # CORRECT: Typography for standalone decorative icons
258
+ pmui.Typography(
259
+ f'<span class="material-icons" style="font-size: 4rem;">lightbulb</span>',
260
+ sizing_mode="fixed", width=60, height=60, sx = {"color": "primary.main"},
261
+ )
262
+ # INCORRECT: IconButton for decorative icons
263
+ pmui.IconButton(icon=icon, disabled=True, ...)
264
+ ```
265
+
266
+ ### Static Components Pattern (Material UI)
267
+ ```python
268
+ import panel as pn
269
+ import panel_material_ui as pmui
270
+ import param
271
+
272
+ pn.extension()
273
+
274
+ class HelloWorld(pn.viewable.Viewer):
275
+ characters = param.Integer(default=10, bounds=(1, 100), doc="Number of characters to display")
276
+
277
+ def _get_kpi_card(self):
278
+ # Create a static layout once
279
+ return pmui.Paper(
280
+ pmui.Column(
281
+ pmui.Typography(
282
+ "📊 Key Performance Metrics",
283
+ variant="h6",
284
+ sx={
285
+ "color": "text.primary",
286
+ "fontWeight": 700,
287
+ "mb": 3,
288
+ "display": "flex",
289
+ "alignItems": "center",
290
+ "gap": 1
291
+ }
292
+ ),
293
+ pmui.Row(
294
+ # Use a reactive/ bound/ reference value for dynamic content
295
+ self.kpi_value
296
+ )
297
+ ),
298
+ )
299
+
300
+ @param.depends("characters")
301
+ def kpi_value(self):
302
+ return f"The kpi is {self.characters}"
303
+
304
+ def __panel__(self):
305
+ return pmui.Paper(
306
+ pmui.Column(
307
+ self.param.characters,
308
+ self._get_kpi_card(),
309
+ ),
310
+ sx={"padding": "20px", "borderRadius": "8px"},
311
+ sizing_mode="stretch_width"
312
+ )
313
+
314
+ if pn.state.served:
315
+ HelloWorld().servable()
316
+ ```
317
+
318
+ **For all other Panel patterns (parameter-driven architecture, reactive updates, serving, etc.), see the Panel Best Practices Guide.**
@@ -0,0 +1,294 @@
1
+ # Panel
2
+
3
+ This guide provides best practices for using Panel. Optimized for LLMs.
4
+
5
+ Please develop code, tests and documentation as an **expert Panel analytics app developer** would do when working with a **short time to market** and in Python .py files.
6
+
7
+ ## Best Practice Hello World App
8
+
9
+ Let's describe our best practices via a basic Hello World App:
10
+
11
+ ```python
12
+ # DO import panel as pn
13
+ import panel as pn
14
+ import param
15
+
16
+ # DO always run pn.extension
17
+ # DO remember to add any imports needed by panes, e.g. pn.extension("tabulator", "plotly", ...)
18
+ # DON'T add "bokeh" as an extension. It is not needed.
19
+ # Do use throttled=True when using slider unless you have a specific reason not to
20
+ pn.extension(throttled=True)
21
+
22
+ # DO organize functions to extract data separately as your app grows. Eventually in a separate data.py file.
23
+ # DO use caching to speed up the app, e.g. for expensive data loading or processing that would return the same result given same input arguments.
24
+ # DO add a ttl (time to live argument) for expensive data loading that changes over time
25
+ @pn.cache(max_items=3)
26
+ def extract(n=5):
27
+ return "Hello World" + "⭐" * n
28
+
29
+ text = extract()
30
+ text_len = len(text)
31
+
32
+ # DO organize functions to transform data separately as your app grows. Eventually in a separate transformations.py file
33
+ # DO add caching to speed up expensive data transformations
34
+ @pn.cache(max_items=3)
35
+ def transform(data: str, count: int=5)->str:
36
+ count = min(count, len(data))
37
+ return data[:count]
38
+
39
+ # DO organize functions to create plots separately as your app grows. Eventually in a separate plots.py file.
40
+ # DO organize custom components and views separately as your app grows. Eventually in separate components.py or views.py file(s).
41
+
42
+ # DO use param.Parameterized, pn.viewable.Viewer or similar approach to create new components and apps with state and reactivity
43
+ class HelloWorld(pn.viewable.Viewer):
44
+ # DO define parameters to hold state and drive the reactivity
45
+ characters = param.Integer(default=text_len, bounds=(0, text_len), doc="Number of characters to display")
46
+
47
+ def __init__(self, **params):
48
+ super().__init__(**params)
49
+
50
+ # DO use sizing_mode="stretch_width" for components unless "fixed" or other sizing_mode is specifically needed
51
+ with pn.config.set(sizing_mode="stretch_width"):
52
+ # DO create widgets using `.from_param` method
53
+ self._characters_input = pn.widgets.IntSlider.from_param(self.param.characters, margin=(10,20))
54
+ # DO Collect input widgets into horizontal, columnar layout unless other layout is specifically needed
55
+ self._inputs = pn.Column(self._characters_input, max_width=300)
56
+ # DO collect output components into some layout like Column, Row, FlexBox or Grid depending on use case
57
+ self._outputs = pn.Column(self.model)
58
+ # DO collect all of your components into a combined layout useful for displaying in notebooks etc.
59
+ self._panel = pn.Row(self._inputs, self._outputs)
60
+
61
+ # DO use caching to speed up bound methods that are expensive to compute or load data and return the same result for a given state of the class.
62
+ # DO add a ttl (time to live argument) for expensive data loading that changes over time.
63
+ @pn.cache(max_items=3)
64
+ # DO prefer .depends over .bind over .rx for reactivity methods on Parameterized classes as it can be typed and documented
65
+ # DON'T use `watch=True` or `.watch(...)` methods to update UI components directly.
66
+ # DO use `watch=True` or `.watch(...)` for updating the state parameters or triggering side effect like saving files or sending email.
67
+ @param.depends("characters")
68
+ def model(self):
69
+ return transform(text, self.characters)
70
+
71
+ # DO provide a method for displaying the component in a notebook setting, i.e. without using a Template or other element that cannot be displayed in a notebook setting.
72
+ def __panel__(self):
73
+ return self._panel
74
+
75
+ # DO provide a method to create a .servable app
76
+ @classmethod
77
+ def create_app(cls, **params):
78
+ instance = cls(**params)
79
+ # DO use a Template or similar page layout for served apps
80
+ template = pn.template.FastListTemplate(
81
+ # DO provide a title for the app
82
+ title="Hello World App",
83
+ # DO provide optional image, optional app description, optional navigation menu, input widgets, optional documentation and optional links in the sidebar
84
+ # DO provide as list of components or a list of single horizontal layout like Column as the sidebar by default is 300 px wide
85
+ sidebar=[instance._inputs],
86
+ # DO provide a list of layouts and output components in the main area of the app.
87
+ # DO use Grid or FlexBox layouts for complex dashboard layouts instead of combination Rows and Columns.
88
+ main=[instance._outputs],
89
+ )
90
+ return template
91
+
92
+ # DO provide a method to serve the app with `python`
93
+ if __name__ == "__main__":
94
+ # DO run with `python path_to_this_file.py`
95
+ HelloWorld.create_app().show(port=5007, autoreload=True, open=True)
96
+ # DO provide a method to serve the app with `panel serve`
97
+ elif pn.state.served:
98
+ # DO run with `panel serve path_to_this_file.py --port 5007 --dev` add `--show` to open the app in a browser
99
+ HelloWorld.create_app().servable()
100
+ ```
101
+
102
+ ```python
103
+ # DO put tests in a separate test file.
104
+ # DO always test that the reactivity works as expected
105
+ def test_characters_reactivity():
106
+ """
107
+ Test characters reactivity.
108
+ """
109
+ # DO test the default values of bound
110
+ hello_world = HelloWorld()
111
+ # DO test the reactivity of bound methods when parameters change
112
+ assert hello_world.model() == text[:hello_world.characters]
113
+ hello_world.characters = 5
114
+ assert hello_world.model() == text[:5]
115
+ hello_world.characters = 3
116
+ assert hello_world.model() == text[:3]
117
+ ```
118
+
119
+ DO note how this test simulates the user's behaviour of loading the page, changing the `characters` input and updating the output without having to write client side tests.
120
+
121
+ ## Key Patterns
122
+
123
+ ### Parameter-Driven Architecture
124
+ - DO use `param.Parameterized` or `pn.viewable.Viewer` classes
125
+ - DO create widgets with `.from_param()` method
126
+ - DO use `@param.depends()` for reactive methods
127
+ - DON'T use `.watch()` for UI updates, only for side effects
128
+
129
+ ### Layouts
130
+ - DO use `pn.Column`, `pn.Row`, `pn.Tabs`, `pn.Accordion` for layouts
131
+ - DO use `pn.template.FastListTemplate` or other templates for served apps
132
+ - DO use `sizing_mode="stretch_width"` by default
133
+
134
+ ### Common Widgets
135
+ - `pn.widgets.IntSlider`, `pn.widgets.Select`, `pn.widgets.DateRangeSlider`
136
+ - `pn.widgets.Tabulator` for tables
137
+ - `pn.pane.Markdown`, `pn.pane.HTML` for content
138
+
139
+ ### Serving
140
+ - `panel serve app.py --dev` for development with hot reload. Add `--show` to open in browser.
141
+ - `app.servable()` to mark components for serving
142
+
143
+ ## Core Principles
144
+
145
+ **Parameter-Driven Design**
146
+ - DO prefer declarative reactive patterns over imperative event handling
147
+ - DO create applications as `param.Parameterized` or `pn.Viewable` classes
148
+ - DO let Parameters drive application state, not widgets directly
149
+ - DO Separate business logic from UI concerns
150
+
151
+ **UI Update Patterns**
152
+ - DO update UI via parameters or `.bind()` and `.depends()` methods
153
+ - DON'T update UI as side effects with `.watch()` methods
154
+ - DO feel free to use `.watch()` for updating app state and non-UI side effects (file saves, emails, etc.)
155
+
156
+ **Component Selection**
157
+ - DO prefer `pn.widgets.Tabulator` for tabular data
158
+ - DO use `pn.extension()` and include needed extensions like "tabulator", "plotly"
159
+ - DON'T include "bokeh" in extensions
160
+
161
+ **Layout**
162
+
163
+ - DO create the overall layout and content structure once, but update the content dynamically. This eliminates flickering and provides a smooth, professional user experience.
164
+ - Don't recreate components when it can be avoided. Instead bind them to reactive functions (pn.bind, methods (@param.depends), expressions (.rx) or parameters (state.param.some_value).
165
+ - DO use sizing_mode='stretch_width' for all components unless you specifically want another `sizing_mode` like `fixed` or `stretch_both`.
166
+ - In a sidebar the order should be: 1) optional image/logo, 2) short app description, 3) input widgets/filters, 4) additional documentation.
167
+
168
+ **Serving**
169
+ - DO use `panel serve app.py --dev` for development with hot reload.
170
+ - DON't use legacy `--autoreload` flag.
171
+ - DO use `if pn.state.served:` to check if being served with `panel serve`
172
+ - DO use `if __name__ == "__main__":` to check if run directly via `python`
173
+
174
+ ```python
175
+ # Correct:
176
+ if pn.state.served:
177
+ main().servable()
178
+
179
+ # Incorrect
180
+ if __name__ == "__main__":
181
+ main().servable()
182
+ ```
183
+
184
+ ## Workflows
185
+
186
+ **Development**
187
+
188
+ - DO always start and keep running a development server `panel serve path_to_file.py --dev` with hot reload while developing!
189
+
190
+ **Testing**
191
+
192
+ - DO structure your code with Parameterized components, so that reactivity and user interactions can be tested easily.
193
+ - DO separate UI logic from business logic to enable unit testing
194
+ - DO separate data extraction, data transformation, plots generation, custom components and views, styles etc. to enable unit testing as your app grows
195
+ - DO always test the reactivity of your app and components.
196
+
197
+ ## Quick Reference
198
+
199
+ ### Widget Creation
200
+ ```python
201
+ # Good: Parameter-driven
202
+ widget = pn.widgets.Select.from_param(self.param.model_type, name="Model Type")
203
+
204
+ # Avoid: Manual management
205
+ widget = pn.widgets.Select(options=['A', 'B'], value='A')
206
+
207
+ # Avoid: Links. They are hard to reason about
208
+ widget.link(self, value='model_type')
209
+ ```
210
+
211
+ ### Reactive Updates
212
+ ```python
213
+ # Best: @param.depends for class methods
214
+ @param.depends('model_results')
215
+ def create_plot(self):
216
+ return create_performance_plot(self.model_results)
217
+
218
+ plot_pane = pn.pane.Matplotlib(self.create_plot)
219
+
220
+ # Best: pn.bind for functions
221
+ plot_pane = pn.pane.Matplotlib(pn.bind(create_plot))
222
+
223
+ # Avoid: Manual updates
224
+ def update_plot(self):
225
+ self.plot_pane.object = create_performance_plot(self.model_results)
226
+ ```
227
+
228
+ ### Static Components Pattern
229
+ ```python
230
+ # DO: Create static layout with reactive content
231
+ def _get_kpi_card(self):
232
+ return pn.pane.HTML(
233
+ pn.Column(
234
+ "📊 Key Performance Metrics",
235
+ self.kpi_value # Reactive reference
236
+ ),
237
+ styles={"padding": "20px", "border": "1px solid #ddd"},
238
+ sizing_mode="stretch_width"
239
+ )
240
+
241
+ @param.depends("characters")
242
+ def kpi_value(self):
243
+ return f"The kpi is {self.characters}"
244
+ ```
245
+
246
+ #### Date time widgets
247
+
248
+ When comparing to data or time values to Pandas series convert to `Timestamp`:
249
+
250
+ ```python
251
+ start_date, end_date = self.date_range
252
+ # DO convert date objects to pandas Timestamp for proper comparison
253
+ start_date = pd.Timestamp(start_date)
254
+ end_date = pd.Timestamp(end_date)
255
+ filtered = filtered[
256
+ (filtered['date'] >= start_date) &
257
+ (filtered['date'] <= end_date)
258
+ ]
259
+ ```
260
+
261
+ ## Components
262
+
263
+ - DO arrange vertically when displaying `CheckButtonGroup` in a sidebar `CheckButtonGroup(..., vertical=True)`.
264
+
265
+ ### Tabulator
266
+
267
+ - DO set `Tabulator.disabled=True` unless you would like the user to be able to edit the table.
268
+
269
+ ## Plotting
270
+
271
+ - DO use bar charts over pie Charts.
272
+
273
+ ### HoloViews/hvPlot
274
+
275
+ - DO let Panel control the renderer theme
276
+ - DON'T set `hv.renderer('bokeh').theme = 'dark_minimal'`
277
+
278
+ DO follow the hvplot and holoviews best practice guides!
279
+
280
+ ### Plotly
281
+
282
+ Do set the template (theme) depending on the `theme` of the app.
283
+
284
+ ```python
285
+ def create_plot(self) -> go.Figure:
286
+ fig = ...
287
+ template = "plotly_dark" if pn.state.theme=="dark" else "plotly_white"
288
+ fig.update_layout(
289
+ template=template, # Change template to align with the theme
290
+ paper_bgcolor='rgba(0,0,0,0)', # Change to transparent background to align with the app background
291
+ plot_bgcolor='rgba(0,0,0,0)' # Change to transparent background to align with the app background
292
+ )
293
+ return fig
294
+ ```