holoviz-mcp 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- holoviz_mcp/__init__.py +18 -0
- holoviz_mcp/apps/__init__.py +1 -0
- holoviz_mcp/apps/configuration_viewer.py +116 -0
- holoviz_mcp/apps/holoviz_get_best_practices.py +173 -0
- holoviz_mcp/apps/holoviz_search.py +319 -0
- holoviz_mcp/apps/hvplot_get_docstring.py +255 -0
- holoviz_mcp/apps/hvplot_get_signature.py +252 -0
- holoviz_mcp/apps/hvplot_list_plot_types.py +83 -0
- holoviz_mcp/apps/panel_get_component.py +496 -0
- holoviz_mcp/apps/panel_get_component_parameters.py +467 -0
- holoviz_mcp/apps/panel_list_components.py +311 -0
- holoviz_mcp/apps/panel_list_packages.py +71 -0
- holoviz_mcp/apps/panel_search_components.py +312 -0
- holoviz_mcp/cli.py +75 -0
- holoviz_mcp/client.py +94 -0
- holoviz_mcp/config/__init__.py +29 -0
- holoviz_mcp/config/config.yaml +178 -0
- holoviz_mcp/config/loader.py +316 -0
- holoviz_mcp/config/models.py +208 -0
- holoviz_mcp/config/resources/best-practices/holoviews.md +423 -0
- holoviz_mcp/config/resources/best-practices/hvplot.md +465 -0
- holoviz_mcp/config/resources/best-practices/panel-material-ui.md +318 -0
- holoviz_mcp/config/resources/best-practices/panel.md +562 -0
- holoviz_mcp/config/schema.json +228 -0
- holoviz_mcp/holoviz_mcp/__init__.py +1 -0
- holoviz_mcp/holoviz_mcp/data.py +970 -0
- holoviz_mcp/holoviz_mcp/models.py +21 -0
- holoviz_mcp/holoviz_mcp/pages_design.md +407 -0
- holoviz_mcp/holoviz_mcp/server.py +220 -0
- holoviz_mcp/hvplot_mcp/__init__.py +1 -0
- holoviz_mcp/hvplot_mcp/server.py +146 -0
- holoviz_mcp/panel_mcp/__init__.py +17 -0
- holoviz_mcp/panel_mcp/data.py +319 -0
- holoviz_mcp/panel_mcp/models.py +124 -0
- holoviz_mcp/panel_mcp/server.py +443 -0
- holoviz_mcp/py.typed +0 -0
- holoviz_mcp/serve.py +36 -0
- holoviz_mcp/server.py +86 -0
- holoviz_mcp/shared/__init__.py +1 -0
- holoviz_mcp/shared/extract_tools.py +74 -0
- holoviz_mcp/thumbnails/configuration_viewer.png +0 -0
- holoviz_mcp/thumbnails/holoviz_get_best_practices.png +0 -0
- holoviz_mcp/thumbnails/holoviz_search.png +0 -0
- holoviz_mcp/thumbnails/hvplot_get_docstring.png +0 -0
- holoviz_mcp/thumbnails/hvplot_get_signature.png +0 -0
- holoviz_mcp/thumbnails/hvplot_list_plot_types.png +0 -0
- holoviz_mcp/thumbnails/panel_get_component.png +0 -0
- holoviz_mcp/thumbnails/panel_get_component_parameters.png +0 -0
- holoviz_mcp/thumbnails/panel_list_components.png +0 -0
- holoviz_mcp/thumbnails/panel_list_packages.png +0 -0
- holoviz_mcp/thumbnails/panel_search_components.png +0 -0
- holoviz_mcp-0.4.0.dist-info/METADATA +216 -0
- holoviz_mcp-0.4.0.dist-info/RECORD +56 -0
- holoviz_mcp-0.4.0.dist-info/WHEEL +4 -0
- holoviz_mcp-0.4.0.dist-info/entry_points.txt +2 -0
- holoviz_mcp-0.4.0.dist-info/licenses/LICENSE.txt +30 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""[hvPlot](https://hvplot.holoviz.org/) MCP Server.
|
|
2
|
+
|
|
3
|
+
This MCP server provides tools, resources, and prompts for using hvPlot to develop quick, interactive
|
|
4
|
+
plots in Python using best practices.
|
|
5
|
+
|
|
6
|
+
Use this server to:
|
|
7
|
+
- List available hvPlot plot types (e.g., 'line', 'scatter', 'bar', ...)
|
|
8
|
+
- Get docstrings and function signatures for hvPlot plot types
|
|
9
|
+
- Access hvPlot documentation and best practices
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Literal
|
|
13
|
+
from typing import Optional
|
|
14
|
+
from typing import Union
|
|
15
|
+
|
|
16
|
+
from fastmcp import Context
|
|
17
|
+
from fastmcp import FastMCP
|
|
18
|
+
|
|
19
|
+
# Create the FastMCP server
|
|
20
|
+
mcp = FastMCP(
|
|
21
|
+
name="hvplot",
|
|
22
|
+
instructions="""
|
|
23
|
+
[hvPlot](https://hvplot.holoviz.org/) MCP Server.
|
|
24
|
+
|
|
25
|
+
This MCP server provides tools, resources, and prompts for using hvPlot to develop quick, interactive plots
|
|
26
|
+
in Python using best practices. Use this server to:
|
|
27
|
+
|
|
28
|
+
- List available hvPlot plot types
|
|
29
|
+
- Get docstrings and function signatures for hvPlot plot types
|
|
30
|
+
- Access hvPlot documentation and best practices""",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _help(
|
|
35
|
+
plot_type: Optional[str] = None, docstring: bool = True, generic: bool = True, style: Union[Literal["matplotlib", "bokeh", "plotly"], bool] = True
|
|
36
|
+
) -> tuple[str, str]:
|
|
37
|
+
"""Retrieve hvPlot docstring and function signature for a plot type."""
|
|
38
|
+
import holoviews as hv
|
|
39
|
+
from hvplot.plotting.core import hvPlot
|
|
40
|
+
from hvplot.util import _get_doc_and_signature
|
|
41
|
+
|
|
42
|
+
if isinstance(style, str):
|
|
43
|
+
hv.extension(style)
|
|
44
|
+
else:
|
|
45
|
+
hv.extension("bokeh")
|
|
46
|
+
|
|
47
|
+
doc, sig = _get_doc_and_signature(cls=hvPlot, kind=plot_type, docstring=docstring, generic=generic, style=style)
|
|
48
|
+
return doc, sig
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@mcp.tool
|
|
52
|
+
async def list_plot_types(ctx: Context) -> list[str]:
|
|
53
|
+
"""
|
|
54
|
+
List all available hvPlot plot types supported in the current environment.
|
|
55
|
+
|
|
56
|
+
Use this tool to discover what plot types you can generate with hvPlot.
|
|
57
|
+
|
|
58
|
+
Note: The plot types are also called "kinds".
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
ctx : Context
|
|
63
|
+
FastMCP context (automatically provided by the MCP framework).
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
list[str]
|
|
68
|
+
Sorted list of all plot type names (e.g., 'line', 'scatter', 'bar', ...).
|
|
69
|
+
|
|
70
|
+
Examples
|
|
71
|
+
--------
|
|
72
|
+
>>> list_plot_types()
|
|
73
|
+
['area', 'bar', 'box', 'contour', ...]
|
|
74
|
+
"""
|
|
75
|
+
from hvplot.converter import HoloViewsConverter
|
|
76
|
+
|
|
77
|
+
return sorted(HoloViewsConverter._kind_mapping)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@mcp.tool
|
|
81
|
+
async def get_docstring(
|
|
82
|
+
ctx: Context, plot_type: str, docstring: bool = True, generic: bool = True, style: Union[Literal["matplotlib", "bokeh", "plotly"], bool] = True
|
|
83
|
+
) -> str:
|
|
84
|
+
"""
|
|
85
|
+
Get the hvPlot docstring for a specific plot type, including available options and usage details.
|
|
86
|
+
|
|
87
|
+
Use this tool to retrieve the full docstring for a plot type, including generic and style options.
|
|
88
|
+
Equivalent to `hvplot.help(plot_type)` in the hvPlot API.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
ctx : Context
|
|
93
|
+
FastMCP context (automatically provided by the MCP framework).
|
|
94
|
+
plot_type : str
|
|
95
|
+
The type of plot to provide help for (e.g., 'line', 'scatter').
|
|
96
|
+
docstring : bool, default=True
|
|
97
|
+
Whether to include the docstring in the output.
|
|
98
|
+
generic : bool, default=True
|
|
99
|
+
Whether to include generic plotting options shared by all plot types.
|
|
100
|
+
style : str or bool, default=True
|
|
101
|
+
Plotting backend to use for style options. If True, automatically infers the backend.
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
str
|
|
106
|
+
The docstring for the specified plot type, including all relevant options and usage information.
|
|
107
|
+
|
|
108
|
+
Examples
|
|
109
|
+
--------
|
|
110
|
+
>>> get_docstring(plot_type='line')
|
|
111
|
+
"""
|
|
112
|
+
doc, _ = _help(plot_type=plot_type, docstring=docstring, generic=generic, style=style)
|
|
113
|
+
return doc
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@mcp.tool
|
|
117
|
+
async def get_signature(ctx: Context, plot_type: str, style: Union[Literal["matplotlib", "bokeh", "plotly"], bool] = True) -> str:
|
|
118
|
+
"""
|
|
119
|
+
Get the function signature for a specific hvPlot plot type.
|
|
120
|
+
|
|
121
|
+
Use this tool to retrieve the Python function signature for a plot type, showing all accepted arguments and their defaults.
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
ctx : Context
|
|
126
|
+
FastMCP context (automatically provided by the MCP framework).
|
|
127
|
+
plot_type : str
|
|
128
|
+
The type of plot to provide help for (e.g., 'line', 'scatter').
|
|
129
|
+
style : str or bool, default=True
|
|
130
|
+
Plotting backend to use for style options. If True, automatically infers the backend (ignored here).
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
str
|
|
135
|
+
The function signature for the specified plot type.
|
|
136
|
+
|
|
137
|
+
Examples
|
|
138
|
+
--------
|
|
139
|
+
>>> get_signature(plot_type='line')
|
|
140
|
+
"""
|
|
141
|
+
_, sig = _help(plot_type=plot_type, docstring=True, generic=True, style=style)
|
|
142
|
+
return str(sig)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
if __name__ == "__main__":
|
|
146
|
+
mcp.run()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Panel MCP Server Package.
|
|
3
|
+
|
|
4
|
+
This package provides Model Context Protocol (MCP) tools for working with Panel,
|
|
5
|
+
the Python library for creating interactive web applications and dashboards.
|
|
6
|
+
|
|
7
|
+
The package includes:
|
|
8
|
+
- Component discovery and introspection tools
|
|
9
|
+
- Parameter information extraction
|
|
10
|
+
- URL proxying utilities for remote environments
|
|
11
|
+
- Data models for component metadata
|
|
12
|
+
|
|
13
|
+
Main modules:
|
|
14
|
+
- server: MCP server implementation with Panel-specific tools
|
|
15
|
+
- data: Component metadata collection and utilities
|
|
16
|
+
- models: Pydantic models for component information
|
|
17
|
+
"""
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Data collection module for Panel component metadata.
|
|
4
|
+
|
|
5
|
+
This module provides functionality to collect metadata about Panel UI components,
|
|
6
|
+
including their documentation, parameter schema, and module information. It supports
|
|
7
|
+
collecting information from panel.viewable.Viewable subclasses across different
|
|
8
|
+
Panel-related packages.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from textwrap import dedent
|
|
16
|
+
|
|
17
|
+
from panel.viewable import Viewable
|
|
18
|
+
|
|
19
|
+
from .models import ComponentDetails
|
|
20
|
+
from .models import ParameterInfo
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def find_all_subclasses(cls: type) -> set[type]:
|
|
24
|
+
"""
|
|
25
|
+
Recursively find all subclasses of a given class.
|
|
26
|
+
|
|
27
|
+
This function performs a depth-first search through the class hierarchy
|
|
28
|
+
to find all classes that inherit from the given base class, either directly
|
|
29
|
+
or through inheritance chains.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
cls : type
|
|
34
|
+
The base class to find subclasses for.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
set[type]
|
|
39
|
+
Set of all subclasses found recursively, not including the base class itself.
|
|
40
|
+
"""
|
|
41
|
+
subclasses = set()
|
|
42
|
+
for subclass in cls.__subclasses__():
|
|
43
|
+
subclasses.add(subclass)
|
|
44
|
+
subclasses.update(find_all_subclasses(subclass))
|
|
45
|
+
return subclasses
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def collect_component_info(cls: type) -> ComponentDetails:
|
|
49
|
+
"""
|
|
50
|
+
Collect comprehensive information about a Panel component class.
|
|
51
|
+
|
|
52
|
+
Extracts metadata including docstring, parameter information, method signatures,
|
|
53
|
+
and other relevant details from a Panel component class. Handles parameter
|
|
54
|
+
introspection safely, converting non-serializable values appropriately.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
cls : type
|
|
59
|
+
The Panel component class to analyze.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
ComponentDetails
|
|
64
|
+
A complete model containing all collected component information.
|
|
65
|
+
"""
|
|
66
|
+
# Extract docstring
|
|
67
|
+
docstring = cls.__doc__ if cls.__doc__ else ""
|
|
68
|
+
|
|
69
|
+
# Extract description (first sentence from docstring)
|
|
70
|
+
description = ""
|
|
71
|
+
if docstring:
|
|
72
|
+
# Clean the docstring and get first sentence
|
|
73
|
+
cleaned_docstring = docstring.strip()
|
|
74
|
+
if cleaned_docstring:
|
|
75
|
+
# Find first sentence ending with period, exclamation, or question mark
|
|
76
|
+
import re
|
|
77
|
+
|
|
78
|
+
sentences = re.split(r"[.!?]", cleaned_docstring)
|
|
79
|
+
if sentences:
|
|
80
|
+
description = sentences[0].strip()
|
|
81
|
+
# Remove leading/trailing whitespace and normalize spaces
|
|
82
|
+
description = " ".join(description.split())
|
|
83
|
+
|
|
84
|
+
# Extract parameters information
|
|
85
|
+
parameters = {}
|
|
86
|
+
if hasattr(cls, "param"):
|
|
87
|
+
for param_name in sorted(cls.param):
|
|
88
|
+
# Skip private parameters
|
|
89
|
+
if param_name.startswith("_"):
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
param_obj = cls.param[param_name]
|
|
93
|
+
param_data = {}
|
|
94
|
+
|
|
95
|
+
# Get common parameter attributes (skip private ones)
|
|
96
|
+
for attr in ["default", "doc", "allow_None", "constant", "readonly", "per_instance"]:
|
|
97
|
+
if hasattr(param_obj, attr) and getattr(param_obj, attr):
|
|
98
|
+
value = getattr(param_obj, attr)
|
|
99
|
+
if isinstance(value, str):
|
|
100
|
+
value = dedent(value).strip()
|
|
101
|
+
# Handle non-JSON serializable values
|
|
102
|
+
try:
|
|
103
|
+
json.dumps(value)
|
|
104
|
+
param_data[attr] = value
|
|
105
|
+
except (TypeError, ValueError):
|
|
106
|
+
param_data[attr] = "NON_JSON_SERIALIZABLE_VALUE"
|
|
107
|
+
|
|
108
|
+
# Get type-specific attributes
|
|
109
|
+
param_type = type(param_obj).__name__
|
|
110
|
+
param_data["type"] = param_type
|
|
111
|
+
|
|
112
|
+
# For Selector parameters, get options
|
|
113
|
+
if hasattr(param_obj, "objects") and param_obj.objects:
|
|
114
|
+
try:
|
|
115
|
+
json.dumps(param_obj.objects)
|
|
116
|
+
param_data["objects"] = param_obj.objects
|
|
117
|
+
except (TypeError, ValueError):
|
|
118
|
+
param_data["objects"] = "NON_JSON_SERIALIZABLE_VALUE"
|
|
119
|
+
|
|
120
|
+
# For Number parameters, get bounds
|
|
121
|
+
if hasattr(param_obj, "bounds") and param_obj.bounds:
|
|
122
|
+
try:
|
|
123
|
+
json.dumps(param_obj.bounds)
|
|
124
|
+
param_data["bounds"] = param_obj.bounds
|
|
125
|
+
except (TypeError, ValueError):
|
|
126
|
+
param_data["bounds"] = "NON_JSON_SERIALIZABLE_VALUE"
|
|
127
|
+
|
|
128
|
+
# For String parameters, get regex
|
|
129
|
+
if hasattr(param_obj, "regex") and param_obj.regex:
|
|
130
|
+
try:
|
|
131
|
+
json.dumps(param_obj.regex)
|
|
132
|
+
param_data["regex"] = param_obj.regex
|
|
133
|
+
except (TypeError, ValueError):
|
|
134
|
+
param_data["regex"] = "NON_JSON_SERIALIZABLE_VALUE"
|
|
135
|
+
|
|
136
|
+
# Create ParameterInfo model
|
|
137
|
+
parameters[param_name] = ParameterInfo(**param_data)
|
|
138
|
+
|
|
139
|
+
# Get __init__ method signature
|
|
140
|
+
init_signature = ""
|
|
141
|
+
if hasattr(cls, "__init__"):
|
|
142
|
+
try:
|
|
143
|
+
import inspect
|
|
144
|
+
|
|
145
|
+
sig = inspect.signature(cls.__init__) # type: ignore[misc]
|
|
146
|
+
init_signature = str(sig)
|
|
147
|
+
except Exception as e:
|
|
148
|
+
init_signature = f"Error getting signature: {e}"
|
|
149
|
+
|
|
150
|
+
# Read reference guide content
|
|
151
|
+
# Create and return ComponentInfo model
|
|
152
|
+
return ComponentDetails(
|
|
153
|
+
name=cls.__name__,
|
|
154
|
+
description=description,
|
|
155
|
+
package=cls.__module__.split(".")[0],
|
|
156
|
+
module_path=f"{cls.__module__}.{cls.__name__}",
|
|
157
|
+
init_signature=init_signature,
|
|
158
|
+
docstring=docstring,
|
|
159
|
+
parameters=parameters,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def get_components(parent=Viewable) -> list[ComponentDetails]:
|
|
164
|
+
"""
|
|
165
|
+
Get detailed information about all Panel component subclasses.
|
|
166
|
+
|
|
167
|
+
Discovers all subclasses of the specified parent class (typically Viewable),
|
|
168
|
+
filters out private classes, and collects comprehensive metadata for each.
|
|
169
|
+
Results are sorted alphabetically by module path for consistency.
|
|
170
|
+
|
|
171
|
+
Parameters
|
|
172
|
+
----------
|
|
173
|
+
parent : type, optional
|
|
174
|
+
The parent class to search for subclasses. Defaults to panel.viewable.Viewable.
|
|
175
|
+
|
|
176
|
+
Returns
|
|
177
|
+
-------
|
|
178
|
+
list[ComponentDetails]
|
|
179
|
+
List of detailed component information models, sorted by module path.
|
|
180
|
+
"""
|
|
181
|
+
all_subclasses = find_all_subclasses(parent)
|
|
182
|
+
|
|
183
|
+
# Filter to only those in panel_material_ui package and exclude private classes
|
|
184
|
+
subclasses = [cls for cls in all_subclasses if not cls.__name__.startswith("_")]
|
|
185
|
+
|
|
186
|
+
# Collect component information
|
|
187
|
+
component_data = [collect_component_info(cls) for cls in subclasses]
|
|
188
|
+
|
|
189
|
+
# Sort by module_path for consistent ordering
|
|
190
|
+
component_data.sort(key=lambda x: x.module_path)
|
|
191
|
+
return component_data
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def save_components(data: list[ComponentDetails], filename: str) -> str:
|
|
195
|
+
"""
|
|
196
|
+
Save component data to a JSON file.
|
|
197
|
+
|
|
198
|
+
Serializes a list of ComponentDetails objects to JSON format for persistence.
|
|
199
|
+
The JSON is formatted with indentation for human readability.
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
data : list[ComponentDetails]
|
|
204
|
+
Component data to save, typically from get_components().
|
|
205
|
+
filename : str
|
|
206
|
+
Path where the JSON file should be created.
|
|
207
|
+
|
|
208
|
+
Returns
|
|
209
|
+
-------
|
|
210
|
+
str
|
|
211
|
+
Absolute path to the created file.
|
|
212
|
+
"""
|
|
213
|
+
filepath = Path(filename)
|
|
214
|
+
|
|
215
|
+
# Convert Pydantic models to dict for JSON serialization
|
|
216
|
+
json_data = [component.model_dump() for component in data]
|
|
217
|
+
|
|
218
|
+
with open(filepath, "w", encoding="utf-8") as f:
|
|
219
|
+
json.dump(json_data, f, indent=2, ensure_ascii=False)
|
|
220
|
+
|
|
221
|
+
return str(filepath)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def load_components(filepath: str) -> list[ComponentDetails]:
|
|
225
|
+
"""
|
|
226
|
+
Load component data from a JSON file.
|
|
227
|
+
|
|
228
|
+
Reads and deserializes component data that was previously saved using
|
|
229
|
+
save_components(). Validates the file exists before attempting to load.
|
|
230
|
+
|
|
231
|
+
Parameters
|
|
232
|
+
----------
|
|
233
|
+
filepath : str
|
|
234
|
+
Path to the saved component data JSON file.
|
|
235
|
+
|
|
236
|
+
Returns
|
|
237
|
+
-------
|
|
238
|
+
list[ComponentDetails]
|
|
239
|
+
Loaded component data as Pydantic model instances.
|
|
240
|
+
|
|
241
|
+
Raises
|
|
242
|
+
------
|
|
243
|
+
FileNotFoundError
|
|
244
|
+
If the specified file does not exist.
|
|
245
|
+
"""
|
|
246
|
+
file_path = Path(filepath)
|
|
247
|
+
|
|
248
|
+
if not file_path.exists():
|
|
249
|
+
raise FileNotFoundError(f"File not found: {filepath}")
|
|
250
|
+
|
|
251
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
252
|
+
json_data = json.load(f)
|
|
253
|
+
|
|
254
|
+
# Convert JSON data back to Pydantic models
|
|
255
|
+
return [ComponentDetails(**item) for item in json_data]
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def to_proxy_url(url: str, jupyter_server_proxy_url: str = "") -> str:
|
|
259
|
+
"""
|
|
260
|
+
Convert localhost URLs to Jupyter server proxy URLs when applicable.
|
|
261
|
+
|
|
262
|
+
This function handles URL conversion for environments where localhost access
|
|
263
|
+
needs to be proxied (like JupyterHub, Binder, etc.). It supports both
|
|
264
|
+
'localhost' and '127.0.0.1' addresses and preserves paths and query parameters.
|
|
265
|
+
|
|
266
|
+
Parameters
|
|
267
|
+
----------
|
|
268
|
+
url : str
|
|
269
|
+
The original URL to potentially convert. Can be any URL, but only
|
|
270
|
+
localhost and 127.0.0.1 URLs will be converted.
|
|
271
|
+
jupyter_server_proxy_url : str, optional
|
|
272
|
+
Base URL for the Jupyter server proxy. If None or empty, no conversion
|
|
273
|
+
is performed. Defaults to the configured proxy URL.
|
|
274
|
+
|
|
275
|
+
Returns
|
|
276
|
+
-------
|
|
277
|
+
str
|
|
278
|
+
The converted proxy URL if applicable, otherwise the original URL.
|
|
279
|
+
Proxy URLs maintain the original port, path, and query parameters.
|
|
280
|
+
|
|
281
|
+
Examples
|
|
282
|
+
--------
|
|
283
|
+
>>> to_proxy_url("http://localhost:5007/app")
|
|
284
|
+
"https://hub.example.com/user/alice/proxy/5007/app"
|
|
285
|
+
|
|
286
|
+
>>> to_proxy_url("https://external.com/page")
|
|
287
|
+
"https://external.com/page" # No conversion for external URLs
|
|
288
|
+
"""
|
|
289
|
+
if jupyter_server_proxy_url and jupyter_server_proxy_url.strip():
|
|
290
|
+
# Check if this is a localhost or 127.0.0.1 URL
|
|
291
|
+
if url.startswith("http://localhost:"):
|
|
292
|
+
# Parse the URL to extract port, path, and query
|
|
293
|
+
url_parts = url.replace("http://localhost:", "")
|
|
294
|
+
elif url.startswith("http://127.0.0.1:"):
|
|
295
|
+
# Parse the URL to extract port, path, and query
|
|
296
|
+
url_parts = url.replace("http://127.0.0.1:", "")
|
|
297
|
+
else:
|
|
298
|
+
# Not a local URL, return original
|
|
299
|
+
proxy_url = url
|
|
300
|
+
return proxy_url
|
|
301
|
+
|
|
302
|
+
# Find the port (everything before the first slash or end of string)
|
|
303
|
+
if "/" in url_parts:
|
|
304
|
+
port = url_parts.split("/", 1)[0]
|
|
305
|
+
path_and_query = "/" + url_parts.split("/", 1)[1]
|
|
306
|
+
else:
|
|
307
|
+
port = url_parts
|
|
308
|
+
path_and_query = "/"
|
|
309
|
+
|
|
310
|
+
# Validate that port is a valid number
|
|
311
|
+
if port and port.isdigit() and 1 <= int(port) <= 65535:
|
|
312
|
+
# Build the proxy URL
|
|
313
|
+
proxy_url = f"{jupyter_server_proxy_url}{port}{path_and_query}"
|
|
314
|
+
else:
|
|
315
|
+
# Invalid port, return original URL
|
|
316
|
+
proxy_url = url
|
|
317
|
+
else:
|
|
318
|
+
proxy_url = url
|
|
319
|
+
return proxy_url
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Pydantic models for Panel component metadata collection.
|
|
2
|
+
|
|
3
|
+
This module defines the data models used to represent Panel UI component information,
|
|
4
|
+
including parameter details, component summaries, and search results.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel
|
|
13
|
+
from pydantic import ConfigDict
|
|
14
|
+
from pydantic import Field
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ParameterInfo(BaseModel):
|
|
18
|
+
"""
|
|
19
|
+
Information about a Panel component parameter.
|
|
20
|
+
|
|
21
|
+
This model captures parameter metadata including type, default value,
|
|
22
|
+
documentation, and type-specific attributes like bounds or options.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
model_config = ConfigDict(extra="allow") # Allow additional fields we don't know about
|
|
26
|
+
|
|
27
|
+
# Common attributes that most parameters have
|
|
28
|
+
type: str = Field(description="The type of the parameter, e.g., 'Parameter', 'Number', 'Selector'.")
|
|
29
|
+
default: Optional[Any] = Field(default=None, description="The default value for the parameter.")
|
|
30
|
+
doc: Optional[str] = Field(default=None, description="Documentation string for the parameter.")
|
|
31
|
+
# Optional attributes that may not be present
|
|
32
|
+
allow_None: Optional[bool] = Field(default=None, description="Whether the parameter accepts None values.")
|
|
33
|
+
constant: Optional[bool] = Field(default=None, description="Whether the parameter is constant (cannot be changed after initialization).")
|
|
34
|
+
readonly: Optional[bool] = Field(default=None, description="Whether the parameter is read-only.")
|
|
35
|
+
per_instance: Optional[bool] = Field(default=None, description="Whether the parameter is per-instance or shared across instances.")
|
|
36
|
+
|
|
37
|
+
# Type-specific attributes (will be present only for relevant parameter types)
|
|
38
|
+
objects: Optional[Any] = Field(default=None, description="Available options for Selector-type parameters.")
|
|
39
|
+
bounds: Optional[Any] = Field(default=None, description="Value bounds for Number-type parameters.")
|
|
40
|
+
regex: Optional[str] = Field(default=None, description="Regular expression pattern for String-type parameters.")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ComponentSummary(BaseModel):
|
|
44
|
+
"""
|
|
45
|
+
High-level information about a Panel UI component.
|
|
46
|
+
|
|
47
|
+
This model provides a compact representation of a component without
|
|
48
|
+
detailed parameter information or docstrings. Used for listings and
|
|
49
|
+
quick overviews.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
module_path: str = Field(description="Full module path of the component, e.g., 'panel.widgets.Button' or 'panel_material_ui.Button'.")
|
|
53
|
+
name: str = Field(description="Name of the component, e.g., 'Button' or 'TextInput'.")
|
|
54
|
+
package: str = Field(description="Package name of the component, e.g., 'panel' or 'panel_material_ui'.")
|
|
55
|
+
description: str = Field(description="Short description of the component's purpose and functionality.")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ComponentSummarySearchResult(ComponentSummary):
|
|
59
|
+
"""
|
|
60
|
+
Component summary with search relevance scoring.
|
|
61
|
+
|
|
62
|
+
Extends ComponentSummary with a relevance score for search results,
|
|
63
|
+
allowing proper ranking and filtering of search matches.
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
relevance_score: int = Field(default=0, description="Relevance score for search results")
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def from_component(cls, component: ComponentDetails, relevance_score: int) -> ComponentSummarySearchResult:
|
|
71
|
+
"""
|
|
72
|
+
Create a search result from a component and relevance score.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
component : ComponentDetails
|
|
77
|
+
The component to create a search result from.
|
|
78
|
+
relevance_score : int
|
|
79
|
+
The relevance score (0-100) for this search result.
|
|
80
|
+
|
|
81
|
+
Returns
|
|
82
|
+
-------
|
|
83
|
+
ComponentSummarySearchResult
|
|
84
|
+
A search result summary of the component.
|
|
85
|
+
"""
|
|
86
|
+
return cls(
|
|
87
|
+
module_path=component.module_path, name=component.name, package=component.package, description=component.description, relevance_score=relevance_score
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ComponentDetails(ComponentSummary):
|
|
92
|
+
"""
|
|
93
|
+
Complete information about a Panel UI component.
|
|
94
|
+
|
|
95
|
+
This model includes all available information about a component:
|
|
96
|
+
summary information, initialization signature, full docstring,
|
|
97
|
+
and detailed parameter specifications.
|
|
98
|
+
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
init_signature: str = Field(description="Signature of the component's __init__ method.")
|
|
102
|
+
docstring: str = Field(description="Docstring of the component, providing detailed information about its usage.")
|
|
103
|
+
parameters: dict[str, ParameterInfo] = Field(
|
|
104
|
+
description="Dictionary of parameters for the component, where keys are parameter names and values are ParameterInfo objects."
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def to_base(self) -> ComponentSummary:
|
|
108
|
+
"""
|
|
109
|
+
Convert to a basic component summary.
|
|
110
|
+
|
|
111
|
+
Strips away detailed information to create a lightweight
|
|
112
|
+
summary suitable for listings and overviews.
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
ComponentSummary
|
|
117
|
+
A summary version of this component.
|
|
118
|
+
"""
|
|
119
|
+
return ComponentSummary(
|
|
120
|
+
module_path=self.module_path,
|
|
121
|
+
name=self.name,
|
|
122
|
+
package=self.package,
|
|
123
|
+
description=self.description,
|
|
124
|
+
)
|