scmcp-shared 0.1.0__py3-none-any.whl → 0.2.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.
scmcp_shared/util.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import inspect
2
2
  import os
3
3
  from pathlib import Path
4
+ from fastmcp.server.dependencies import get_context
4
5
 
5
6
 
6
7
 
@@ -8,12 +9,13 @@ def get_env(key):
8
9
  return os.environ.get(f"SCMCP_{key.upper()}")
9
10
 
10
11
 
11
- def filter_args(request, func):
12
- # sometime,it is a bit redundant, but I think it adds robustness in case the function parameters change
12
+ def filter_args(request, func, **extra_kwargs):
13
13
  kwargs = request.model_dump()
14
14
  args = request.model_fields_set
15
15
  parameters = inspect.signature(func).parameters
16
+ extra_kwargs = {k: extra_kwargs[k] for k in extra_kwargs if k in parameters}
16
17
  func_kwargs = {k: kwargs.get(k) for k in args if k in parameters}
18
+ func_kwargs.update(extra_kwargs)
17
19
  return func_kwargs
18
20
 
19
21
 
@@ -55,25 +57,44 @@ def add_op_log(adata, func, kwargs):
55
57
 
56
58
 
57
59
 
58
- def savefig(fig, file):
60
+ def savefig(axes, file):
61
+ from matplotlib.axes import Axes
62
+
59
63
  try:
60
64
  file_path = Path(file)
61
65
  file_path.parent.mkdir(parents=True, exist_ok=True)
62
- if hasattr(fig, 'figure'): # if Axes
63
- fig.figure.savefig(file_path)
64
- elif hasattr(fig, 'save'): # for plotnine.ggplot.ggplot
65
- fig.save(file_path)
66
- else: # if Figure
67
- fig.savefig(file_path)
66
+ if isinstance(axes, list):
67
+ if isinstance(axes[0], Axes):
68
+ axes[0].figure.savefig(file_path)
69
+ elif isinstance(axes, dict):
70
+ ax = list(axes.values())[0]
71
+ if isinstance(ax, Axes):
72
+ ax.figure.savefig(file_path)
73
+ elif isinstance(axes, Axes):
74
+ axes.figure.savefig(file_path)
75
+ elif hasattr(axes, 'savefig'): # if Figure
76
+ axes.savefig(file_path)
77
+ elif hasattr(axes, 'save'): # for plotnine.ggplot.ggplot
78
+ axes.save(file_path)
79
+ else:
80
+ raise ValueError(f"axes must be a Axes or plotnine object, but got {type(axes)}")
68
81
  return file_path
69
82
  except Exception as e:
70
83
  raise e
71
84
 
72
85
 
73
- def set_fig_path(func, fig=None, **kwargs):
74
- "maybe I need to save figure by myself, instead of using scanpy save function..."
75
- fig_dir = Path(os.getcwd()) / "figures"
86
+ def set_fig_path(axes, func=None, **kwargs):
87
+ if hasattr(func, "func") and hasattr(func.func, "__name__"):
88
+ # For partial functions, use the original function name
89
+ func_name = func.func.__name__
90
+ elif hasattr(func, "__name__"):
91
+ func_name = func.__name__
92
+ elif hasattr(func, "__class__"):
93
+ func_name = func.__class__.__name__
94
+ else:
95
+ func_name = str(func)
76
96
 
97
+ fig_dir = Path(os.getcwd()) / "figures"
77
98
  kwargs.pop("save", None)
78
99
  kwargs.pop("show", None)
79
100
  args = []
@@ -83,40 +104,13 @@ def set_fig_path(func, fig=None, **kwargs):
83
104
  else:
84
105
  args.append(f"{k}-{v}")
85
106
  args_str = "_".join(args)
86
- if func == "rank_genes_groups_dotplot":
87
- old_path = fig_dir / 'dotplot_.png'
88
- fig_path = fig_dir / f"{func}_{args_str}.png"
89
- elif func in ["scatter", "embedding"]:
90
- if "basis" in kwargs and kwargs['basis'] is not None:
91
- old_path = fig_dir / f"{kwargs['basis']}.png"
92
- fig_path = fig_dir / f"{func}_{args_str}.png"
93
- else:
94
- old_path = fig_dir / f"{func}.png"
95
- fig_path = fig_dir / f"{func}_{args_str}.png"
96
- elif func == "highly_variable_genes":
97
- old_path = fig_dir / 'filter_genes_dispersion.png'
98
- fig_path = fig_dir / f"{func}_{args_str}.png"
99
- elif func == "scvelo_projection":
100
- old_path = fig_dir / f"scvelo_{kwargs['kernel']}.png"
101
- fig_path = fig_dir / f"{func}_{args_str}.png"
102
- else:
103
- if (fig_dir / f"{func}_.png").is_file():
104
- old_path = fig_dir / f"{func}_.png"
105
- else:
106
- old_path = fig_dir / f"{func}.png"
107
- fig_path = fig_dir / f"{func}_{args_str}.png"
107
+ fig_path = fig_dir / f"{func_name}_{args_str}.png"
108
108
  try:
109
- if fig is not None:
110
- savefig(fig, fig_path)
111
- else:
112
- os.rename(old_path, fig_path)
113
- return fig_path
114
- except FileNotFoundError:
115
- print(f"The file {old_path} does not exist")
116
- except FileExistsError:
117
- print(f"The file {fig_path} already exists")
109
+ savefig(axes, fig_path)
118
110
  except PermissionError:
119
- print("You don't have permission to rename this file")
111
+ raise PermissionError("You don't have permission to rename this file")
112
+ except Exception as e:
113
+ raise e
120
114
  transport = get_env("TRANSPORT")
121
115
  if transport == "stdio":
122
116
  return fig_path
@@ -134,7 +128,6 @@ async def get_figure(request):
134
128
  figure_name = request.path_params["figure_name"]
135
129
  figure_path = f"./figures/{figure_name}"
136
130
 
137
- # 检查文件是否存在
138
131
  if not os.path.isfile(figure_path):
139
132
  return Response(content={"error": "figure not found"}, media_type="application/json")
140
133
 
@@ -174,7 +167,6 @@ async def get_figure(request):
174
167
  figure_name = request.path_params["figure_name"]
175
168
  figure_path = f"./figures/{figure_name}"
176
169
 
177
- # 检查文件是否存在
178
170
  if not os.path.isfile(figure_path):
179
171
  return Response(content={"error": "figure not found"}, media_type="application/json")
180
172
 
@@ -183,4 +175,35 @@ async def get_figure(request):
183
175
 
184
176
  def add_figure_route(server):
185
177
  from starlette.routing import Route
186
- server._additional_http_routes = [Route("/figures/{figure_name}", endpoint=get_figure)]
178
+ server._additional_http_routes = [Route("/figures/{figure_name}", endpoint=get_figure)]
179
+
180
+
181
+ def get_ads():
182
+ ctx = get_context()
183
+ ads = ctx.request_context.lifespan_context
184
+ return ads
185
+
186
+
187
+ def generate_msg(request, adata, ads):
188
+ kwargs = request.model_dump()
189
+ sampleid = kwargs.get("sampleid")
190
+ dtype = kwargs.get("dtype", "exp")
191
+ return {"sampleid": sampleid or ads.active_id, "dtype": dtype, "adata": adata}
192
+
193
+
194
+ def sc_like_plot(plot_func, adata, request, **kwargs):
195
+ func_kwargs = filter_args(request, plot_func, show=False, save=False)
196
+ axes = plot_func(adata, **func_kwargs)
197
+ fig_path = set_fig_path(axes, plot_func, **func_kwargs)
198
+ add_op_log(adata, plot_func, func_kwargs)
199
+ return fig_path
200
+
201
+
202
+ async def filter_tools(mcp, include_tools=None, exclude_tools=None):
203
+ tools = await mcp.get_tools()
204
+ for tool in tools:
205
+ if exclude_tools and tool in exclude_tools:
206
+ mcp.remove_tool(tool)
207
+ if include_tools and tool not in include_tools:
208
+ mcp.remove_tool(tool)
209
+ return mcp
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scmcp_shared
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A shared function libray for scmcphub
5
5
  Author-email: shuang <hsh-me@outlook.com>
6
6
  License: BSD 3-Clause License
@@ -33,7 +33,7 @@ License: BSD 3-Clause License
33
33
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
34
  License-File: LICENSE
35
35
  Requires-Python: >=3.10
36
- Requires-Dist: fastmcp>=2.3.0
36
+ Requires-Dist: fastmcp>=2.3.4
37
37
  Requires-Dist: mcp>=1.8.0
38
38
  Requires-Dist: pydantic
39
39
  Requires-Dist: scanpy
@@ -0,0 +1,20 @@
1
+ scmcp_shared/__init__.py,sha256=ZJMoFGVU2-tXepjhh5Fhy8ZyNHtOIGZWbJygHmqwmCA,24
2
+ scmcp_shared/logging_config.py,sha256=eCuLuyxMmbj8A1E0VqYWoKA5JPTSbo6cmjS4LOyd0RQ,872
3
+ scmcp_shared/util.py,sha256=Ul88pY0aHFN42tiWwexO5LfQJgNslyOD4Jb9HTMy3uI,6814
4
+ scmcp_shared/schema/__init__.py,sha256=fwMJT4mQ3rvRJpSz9ruwwZU1GbXsUYPVQRpP56Az_JM,28
5
+ scmcp_shared/schema/base.py,sha256=hWh0534xon4gyIxwXUBVR067uFiW73nZccSs6DkcwYc,326
6
+ scmcp_shared/schema/io.py,sha256=yxa0BGQXuHCUvv6ZGQApPqMOlyJuC-eQNk7jlRGsMJ8,4913
7
+ scmcp_shared/schema/pl.py,sha256=I9SCJgjmfB0VTNio4uNnP26ajWCnsHPsDoO-yMuzYVo,29578
8
+ scmcp_shared/schema/pp.py,sha256=Uhe28zD71UDZbziesH6bgQXIRXQz_HHrr8-Hb4RQsKI,21696
9
+ scmcp_shared/schema/tl.py,sha256=QV0dP-5ZtEKUDmSV9m5ryx7uQub4rw4in7PYsxId5nU,34485
10
+ scmcp_shared/schema/util.py,sha256=R0MThHKKGYGGJu-hMc-i7fomW-6ugaoQqPi_hbKrB5A,4844
11
+ scmcp_shared/server/__init__.py,sha256=vGYGUpLt8XHRbJI84Ox6WnK4ntzsbTal99Uu14nav60,1796
12
+ scmcp_shared/server/io.py,sha256=agioBQeTREqOX8qJsP2aOTkaU3dT04uvObWhUSfOPTg,2422
13
+ scmcp_shared/server/pl.py,sha256=0KIB9n-h5L3F4TOO_ezPAEX7TVCVBOsiZd6bqD5WbGQ,11126
14
+ scmcp_shared/server/pp.py,sha256=iS-GrU2-Fyw0phLI4A44g2Y3LpFfYF7AWU5ueyzLFxY,12116
15
+ scmcp_shared/server/tl.py,sha256=k8GCTmrFh63jI5REO4-BmfCgyvcGgNpvtsxrn_0K67k,13228
16
+ scmcp_shared/server/util.py,sha256=eBkwWUSrndTb6RHujgXGZYU1aitJEocEuV6U5IOLsws,9030
17
+ scmcp_shared-0.2.0.dist-info/METADATA,sha256=Ayh--1Js_wfAHx1Wep-tw0fxihV9ikUYRWttSwAZL88,2099
18
+ scmcp_shared-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
+ scmcp_shared-0.2.0.dist-info/licenses/LICENSE,sha256=YNr1hpea195yq-wGtB8j-2dGtt7A5G00WENmxa7JGco,1495
20
+ scmcp_shared-0.2.0.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- scmcp_shared/__init__.py,sha256=2Ff5FiaFaDmrGrcJagyK7JKuraTfI0CnRahAnXLS53w,24
2
- scmcp_shared/logging_config.py,sha256=eCuLuyxMmbj8A1E0VqYWoKA5JPTSbo6cmjS4LOyd0RQ,872
3
- scmcp_shared/util.py,sha256=jY-HbMzpqKQpzm0UhiyrUwtjtx31_KKUmewlyZYpfZA,6300
4
- scmcp_shared/schema/__init__.py,sha256=uEIJaRLssrbI9KYOH3RxtMRU63Bes9SkLBskxNBt0hA,18
5
- scmcp_shared/schema/io.py,sha256=V6Bv6No_FH0Ps2E6VkkHBu7z2EQku9XcG4iWkxMPjiU,4965
6
- scmcp_shared/schema/pl.py,sha256=yP9XnotdEvY6iPKfvz0otfJFyDJh7FkaIJx7FG4Em44,29548
7
- scmcp_shared/schema/pp.py,sha256=aAHECfM1LSCRDZkgtPpoMxLXurEyVdG_9dyhj0HSOlw,23507
8
- scmcp_shared/schema/tl.py,sha256=VNoNBf7hFF_4SG31vNYX52i0M38Bj6ryBJpCJYtedVw,32848
9
- scmcp_shared/schema/util.py,sha256=rMb29eCu2hVhkggYeOPA2b72Aa8VPDq58nd2Rz8OmoI,4652
10
- scmcp_shared/server/__init__.py,sha256=V4eLId2ZvwoPTE_53uimF02hLnY8X9iXlA4ri-WfvjM,22
11
- scmcp_shared/server/io.py,sha256=AQ90QtQdfv2IFo_IPU6dmfk1redF3HPt30DHHv5_3qo,2815
12
- scmcp_shared-0.1.0.dist-info/METADATA,sha256=VpPvS7tbQQJLtsCNbCJfIAi-iiDf2mm9VCBdqaelcbg,2099
13
- scmcp_shared-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
14
- scmcp_shared-0.1.0.dist-info/licenses/LICENSE,sha256=YNr1hpea195yq-wGtB8j-2dGtt7A5G00WENmxa7JGco,1495
15
- scmcp_shared-0.1.0.dist-info/RECORD,,