scmcp-shared 0.3.7__py3-none-any.whl → 0.5.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.
Files changed (172) hide show
  1. scmcp_shared/__init__.py +1 -3
  2. scmcp_shared/agent.py +80 -0
  3. scmcp_shared/backend.py +44 -0
  4. scmcp_shared/cli.py +76 -37
  5. scmcp_shared/kb.py +38 -0
  6. scmcp_shared/logging_config.py +6 -8
  7. scmcp_shared/mcp_base.py +184 -0
  8. scmcp_shared/schema/io.py +101 -59
  9. scmcp_shared/schema/pl.py +386 -490
  10. scmcp_shared/schema/pp.py +514 -265
  11. scmcp_shared/schema/preset/__init__.py +15 -0
  12. scmcp_shared/schema/preset/io.py +103 -0
  13. scmcp_shared/schema/preset/pl.py +843 -0
  14. scmcp_shared/schema/preset/pp.py +616 -0
  15. scmcp_shared/schema/preset/tl.py +917 -0
  16. scmcp_shared/schema/preset/util.py +123 -0
  17. scmcp_shared/schema/tl.py +355 -407
  18. scmcp_shared/schema/tool.py +11 -0
  19. scmcp_shared/schema/util.py +57 -72
  20. scmcp_shared/server/__init__.py +0 -12
  21. scmcp_shared/server/auto.py +54 -0
  22. scmcp_shared/server/code.py +3 -0
  23. scmcp_shared/server/preset/__init__.py +14 -0
  24. scmcp_shared/server/{io.py → preset/io.py} +26 -22
  25. scmcp_shared/server/{pl.py → preset/pl.py} +162 -78
  26. scmcp_shared/server/{pp.py → preset/pp.py} +127 -65
  27. scmcp_shared/server/{tl.py → preset/tl.py} +142 -79
  28. scmcp_shared/server/{util.py → preset/util.py} +123 -66
  29. scmcp_shared/util.py +109 -38
  30. scmcp_shared/vector_db/decoupler.lance/_transactions/0-9499b1b5-85d4-44c2-8f05-1bcce87fe4ef.txn +3 -0
  31. scmcp_shared/vector_db/decoupler.lance/_transactions/1-1632e7a3-4427-4077-8d03-57437144443d.txn +0 -0
  32. scmcp_shared/vector_db/decoupler.lance/_transactions/10-dcf66479-eafb-4193-9358-198154aea1c1.txn +0 -0
  33. scmcp_shared/vector_db/decoupler.lance/_transactions/11-2c6ddc17-49f5-47b5-8764-753297cf9e1b.txn +0 -0
  34. scmcp_shared/vector_db/decoupler.lance/_transactions/12-f079d0a2-9c1c-4e7a-abf6-3e3c4cddac11.txn +0 -0
  35. scmcp_shared/vector_db/decoupler.lance/_transactions/13-5bda9382-a06e-493d-85cb-e066172778ce.txn +0 -0
  36. scmcp_shared/vector_db/decoupler.lance/_transactions/14-6f9c669f-25e2-4096-b7ea-9b421a37e110.txn +0 -0
  37. scmcp_shared/vector_db/decoupler.lance/_transactions/15-2068cca9-31e8-45a3-86d1-bcb8924c72b9.txn +0 -0
  38. scmcp_shared/vector_db/decoupler.lance/_transactions/16-72356bb2-5c98-424e-97aa-a92fa3453da6.txn +0 -0
  39. scmcp_shared/vector_db/decoupler.lance/_transactions/17-9baa67ce-f6d3-478c-9511-20d01988d4d5.txn +0 -0
  40. scmcp_shared/vector_db/decoupler.lance/_transactions/18-b9f2e28e-c4c5-4ce7-bf58-402d53a39558.txn +0 -0
  41. scmcp_shared/vector_db/decoupler.lance/_transactions/19-d199d9f9-7990-4ec0-adde-e98c1c927202.txn +0 -0
  42. scmcp_shared/vector_db/decoupler.lance/_transactions/2-3827747c-095e-41cb-af69-3814bab3a588.txn +0 -0
  43. scmcp_shared/vector_db/decoupler.lance/_transactions/20-31c197a3-7a23-472f-a7e3-056c0ff11e3a.txn +0 -0
  44. scmcp_shared/vector_db/decoupler.lance/_transactions/21-066e3024-36f8-4557-83ae-d2f338ec045c.txn +0 -0
  45. scmcp_shared/vector_db/decoupler.lance/_transactions/22-1d1ccbe5-7c4c-4882-986a-c61ec2f7925e.txn +0 -0
  46. scmcp_shared/vector_db/decoupler.lance/_transactions/23-b2c41628-b2d6-49f1-81b5-3cd9100894b4.txn +0 -0
  47. scmcp_shared/vector_db/decoupler.lance/_transactions/24-e43e2e8d-9dd8-479f-b63c-d147e737c49f.txn +0 -0
  48. scmcp_shared/vector_db/decoupler.lance/_transactions/25-188708e9-5ee1-4b5c-8e13-45dec8efd60d.txn +0 -0
  49. scmcp_shared/vector_db/decoupler.lance/_transactions/26-4f4b6316-5680-4b31-8dd6-215cce66d7b5.txn +0 -0
  50. scmcp_shared/vector_db/decoupler.lance/_transactions/27-c1855f86-abab-44ef-9e61-1312dffc9b06.txn +0 -0
  51. scmcp_shared/vector_db/decoupler.lance/_transactions/28-650d2be0-e977-4177-b457-b733eea8b6ea.txn +0 -0
  52. scmcp_shared/vector_db/decoupler.lance/_transactions/29-e47751d8-0e44-46da-82bd-4836b00a8431.txn +0 -0
  53. scmcp_shared/vector_db/decoupler.lance/_transactions/3-518fc9b8-67de-48f7-94c8-f2398c63dbc5.txn +0 -0
  54. scmcp_shared/vector_db/decoupler.lance/_transactions/30-0db3ea0f-deca-4b5b-9d70-9fb5f741f90c.txn +0 -0
  55. scmcp_shared/vector_db/decoupler.lance/_transactions/31-ef76f0bb-bcfa-4031-b3ad-94b51ee818a9.txn +0 -0
  56. scmcp_shared/vector_db/decoupler.lance/_transactions/32-a763e0f1-5c8d-460f-a455-0c6f6c4f1450.txn +0 -0
  57. scmcp_shared/vector_db/decoupler.lance/_transactions/33-30bce2aa-8e6f-4f42-a323-c420133aa20f.txn +0 -0
  58. scmcp_shared/vector_db/decoupler.lance/_transactions/34-f91ec2f9-d34b-4fcf-8bc7-564c28e50538.txn +0 -0
  59. scmcp_shared/vector_db/decoupler.lance/_transactions/35-7f36a75c-96a7-4868-a4db-5d5f78ecf850.txn +0 -0
  60. scmcp_shared/vector_db/decoupler.lance/_transactions/36-f2df7f99-a37c-458a-958d-7eb5e81eee18.txn +0 -0
  61. scmcp_shared/vector_db/decoupler.lance/_transactions/37-e0af7415-955b-4167-8705-fa9ddb643e9a.txn +0 -0
  62. scmcp_shared/vector_db/decoupler.lance/_transactions/38-6c24a9f0-ce71-4dda-8193-479aa23c5456.txn +0 -0
  63. scmcp_shared/vector_db/decoupler.lance/_transactions/39-72b8a20c-3112-4cf1-8b05-b74a2c02c3f2.txn +0 -0
  64. scmcp_shared/vector_db/decoupler.lance/_transactions/4-42904942-5a79-4f28-90ce-35a4ae8c40d1.txn +0 -0
  65. scmcp_shared/vector_db/decoupler.lance/_transactions/40-95ac7526-6a2e-4914-9654-288c41bff370.txn +0 -0
  66. scmcp_shared/vector_db/decoupler.lance/_transactions/41-13430464-5e62-4aa8-9709-513693a75095.txn +0 -0
  67. scmcp_shared/vector_db/decoupler.lance/_transactions/42-cbb1ad83-a906-4540-bc37-4db158eac618.txn +0 -0
  68. scmcp_shared/vector_db/decoupler.lance/_transactions/43-3c246401-d742-49a9-b24d-cfc457685461.txn +0 -0
  69. scmcp_shared/vector_db/decoupler.lance/_transactions/44-11315c14-ecf4-4e3d-8690-46d57cb3e8c0.txn +0 -0
  70. scmcp_shared/vector_db/decoupler.lance/_transactions/45-7bbf4bc6-96d4-425a-afa2-34aec7121c85.txn +0 -0
  71. scmcp_shared/vector_db/decoupler.lance/_transactions/5-82dda0b4-7838-4f04-90f3-4b1e932c6891.txn +0 -0
  72. scmcp_shared/vector_db/decoupler.lance/_transactions/6-c78352d8-16ba-4814-bd7f-74b8c0c5efe7.txn +0 -0
  73. scmcp_shared/vector_db/decoupler.lance/_transactions/7-bb882b35-63f3-4c52-870f-6a64e3ac7f3c.txn +0 -0
  74. scmcp_shared/vector_db/decoupler.lance/_transactions/8-fc84ad1b-1b59-4822-8fc7-70f4eb18f6d9.txn +0 -0
  75. scmcp_shared/vector_db/decoupler.lance/_transactions/9-3404dcf1-bb17-40e6-9e5d-d9942dff90b2.txn +0 -0
  76. scmcp_shared/vector_db/decoupler.lance/_versions/1.manifest +0 -0
  77. scmcp_shared/vector_db/decoupler.lance/_versions/10.manifest +0 -0
  78. scmcp_shared/vector_db/decoupler.lance/_versions/11.manifest +0 -0
  79. scmcp_shared/vector_db/decoupler.lance/_versions/12.manifest +0 -0
  80. scmcp_shared/vector_db/decoupler.lance/_versions/13.manifest +0 -0
  81. scmcp_shared/vector_db/decoupler.lance/_versions/14.manifest +0 -0
  82. scmcp_shared/vector_db/decoupler.lance/_versions/15.manifest +0 -0
  83. scmcp_shared/vector_db/decoupler.lance/_versions/16.manifest +0 -0
  84. scmcp_shared/vector_db/decoupler.lance/_versions/17.manifest +0 -0
  85. scmcp_shared/vector_db/decoupler.lance/_versions/18.manifest +0 -0
  86. scmcp_shared/vector_db/decoupler.lance/_versions/19.manifest +0 -0
  87. scmcp_shared/vector_db/decoupler.lance/_versions/2.manifest +0 -0
  88. scmcp_shared/vector_db/decoupler.lance/_versions/20.manifest +0 -0
  89. scmcp_shared/vector_db/decoupler.lance/_versions/21.manifest +0 -0
  90. scmcp_shared/vector_db/decoupler.lance/_versions/22.manifest +0 -0
  91. scmcp_shared/vector_db/decoupler.lance/_versions/23.manifest +0 -0
  92. scmcp_shared/vector_db/decoupler.lance/_versions/24.manifest +0 -0
  93. scmcp_shared/vector_db/decoupler.lance/_versions/25.manifest +0 -0
  94. scmcp_shared/vector_db/decoupler.lance/_versions/26.manifest +0 -0
  95. scmcp_shared/vector_db/decoupler.lance/_versions/27.manifest +0 -0
  96. scmcp_shared/vector_db/decoupler.lance/_versions/28.manifest +0 -0
  97. scmcp_shared/vector_db/decoupler.lance/_versions/29.manifest +0 -0
  98. scmcp_shared/vector_db/decoupler.lance/_versions/3.manifest +0 -0
  99. scmcp_shared/vector_db/decoupler.lance/_versions/30.manifest +0 -0
  100. scmcp_shared/vector_db/decoupler.lance/_versions/31.manifest +0 -0
  101. scmcp_shared/vector_db/decoupler.lance/_versions/32.manifest +0 -0
  102. scmcp_shared/vector_db/decoupler.lance/_versions/33.manifest +0 -0
  103. scmcp_shared/vector_db/decoupler.lance/_versions/34.manifest +0 -0
  104. scmcp_shared/vector_db/decoupler.lance/_versions/35.manifest +0 -0
  105. scmcp_shared/vector_db/decoupler.lance/_versions/36.manifest +0 -0
  106. scmcp_shared/vector_db/decoupler.lance/_versions/37.manifest +0 -0
  107. scmcp_shared/vector_db/decoupler.lance/_versions/38.manifest +0 -0
  108. scmcp_shared/vector_db/decoupler.lance/_versions/39.manifest +0 -0
  109. scmcp_shared/vector_db/decoupler.lance/_versions/4.manifest +0 -0
  110. scmcp_shared/vector_db/decoupler.lance/_versions/40.manifest +0 -0
  111. scmcp_shared/vector_db/decoupler.lance/_versions/41.manifest +0 -0
  112. scmcp_shared/vector_db/decoupler.lance/_versions/42.manifest +0 -0
  113. scmcp_shared/vector_db/decoupler.lance/_versions/43.manifest +0 -0
  114. scmcp_shared/vector_db/decoupler.lance/_versions/44.manifest +0 -0
  115. scmcp_shared/vector_db/decoupler.lance/_versions/45.manifest +0 -0
  116. scmcp_shared/vector_db/decoupler.lance/_versions/46.manifest +0 -0
  117. scmcp_shared/vector_db/decoupler.lance/_versions/5.manifest +0 -0
  118. scmcp_shared/vector_db/decoupler.lance/_versions/6.manifest +0 -0
  119. scmcp_shared/vector_db/decoupler.lance/_versions/7.manifest +0 -0
  120. scmcp_shared/vector_db/decoupler.lance/_versions/8.manifest +0 -0
  121. scmcp_shared/vector_db/decoupler.lance/_versions/9.manifest +0 -0
  122. scmcp_shared/vector_db/decoupler.lance/data/0cc94dc3-2ca3-403c-a9df-59753f24b3f2.lance +0 -0
  123. scmcp_shared/vector_db/decoupler.lance/data/254a1a71-cdd2-4cca-b7ec-a50540fed62c.lance +0 -0
  124. scmcp_shared/vector_db/decoupler.lance/data/2574d216-5f05-4794-9e1c-986fafde6ab4.lance +0 -0
  125. scmcp_shared/vector_db/decoupler.lance/data/26c7bbe7-5f97-4453-b065-123b764448b3.lance +0 -0
  126. scmcp_shared/vector_db/decoupler.lance/data/286595a6-88f6-4b05-861e-e4f607a6fcdb.lance +0 -0
  127. scmcp_shared/vector_db/decoupler.lance/data/33219b1a-7575-46ef-bb64-2bdbde1e692b.lance +0 -0
  128. scmcp_shared/vector_db/decoupler.lance/data/344ad7dd-98bb-41de-b347-2f17bf5735cb.lance +0 -0
  129. scmcp_shared/vector_db/decoupler.lance/data/395b1ecb-68fe-4dd3-a770-4b3ed393ae0c.lance +0 -0
  130. scmcp_shared/vector_db/decoupler.lance/data/3db97f4c-9c35-44b7-9042-82b4006c5a22.lance +0 -0
  131. scmcp_shared/vector_db/decoupler.lance/data/4540246e-b0bb-4f4d-8f98-60a64f1c42ed.lance +0 -0
  132. scmcp_shared/vector_db/decoupler.lance/data/4643dbc0-9e45-4b63-81e1-e06cf9bddcec.lance +0 -0
  133. scmcp_shared/vector_db/decoupler.lance/data/4f28eb52-d409-43f2-ae17-d1826f209006.lance +0 -0
  134. scmcp_shared/vector_db/decoupler.lance/data/4fda4a22-35f0-4897-b4c5-6cedefba66e6.lance +0 -0
  135. scmcp_shared/vector_db/decoupler.lance/data/51ee77b6-ab24-44c0-916e-b81ff3912731.lance +0 -0
  136. scmcp_shared/vector_db/decoupler.lance/data/539d15c9-e88b-400c-9ab7-08f5e8dca10d.lance +0 -0
  137. scmcp_shared/vector_db/decoupler.lance/data/54007132-91f5-4909-8678-c471ef4d100f.lance +0 -0
  138. scmcp_shared/vector_db/decoupler.lance/data/546d88ec-0d5c-42bb-bccc-3271f3f183a4.lance +0 -0
  139. scmcp_shared/vector_db/decoupler.lance/data/6912ad8a-343c-4ca1-b1b7-4300125e688e.lance +0 -0
  140. scmcp_shared/vector_db/decoupler.lance/data/6b72e4f7-3051-40a4-a492-6d9eff1c647d.lance +0 -0
  141. scmcp_shared/vector_db/decoupler.lance/data/6d7ee320-ae1c-4e49-8ce5-c85971b11ce6.lance +0 -0
  142. scmcp_shared/vector_db/decoupler.lance/data/6ea47b70-69d4-43e9-b2c6-442fe50e4dd1.lance +0 -0
  143. scmcp_shared/vector_db/decoupler.lance/data/72adb5ed-bf11-4d06-b146-0272d333db08.lance +0 -0
  144. scmcp_shared/vector_db/decoupler.lance/data/745907df-e261-4f4b-a757-2c01c058bf27.lance +0 -0
  145. scmcp_shared/vector_db/decoupler.lance/data/78988307-4c66-4ad7-b295-7463ecd53609.lance +0 -0
  146. scmcp_shared/vector_db/decoupler.lance/data/798ce305-6d60-4f28-b82a-e1f647907abf.lance +0 -0
  147. scmcp_shared/vector_db/decoupler.lance/data/7c159cce-2741-442b-9c63-f44bf2996718.lance +0 -0
  148. scmcp_shared/vector_db/decoupler.lance/data/7cd7a818-e68b-4fa8-bad3-23b60c386670.lance +0 -0
  149. scmcp_shared/vector_db/decoupler.lance/data/7ec553ed-0c7a-4bac-99b2-2ad976b40466.lance +0 -0
  150. scmcp_shared/vector_db/decoupler.lance/data/83efdc85-d990-4762-b69a-fb1c3938f60f.lance +0 -0
  151. scmcp_shared/vector_db/decoupler.lance/data/8c1613b3-8d69-49c5-bfd0-a021bb516faf.lance +0 -0
  152. scmcp_shared/vector_db/decoupler.lance/data/95cd1b22-1f93-4133-9841-de21549ad8c5.lance +0 -0
  153. scmcp_shared/vector_db/decoupler.lance/data/9a2ea5d4-087e-4d3a-b64b-6c568b8a0010.lance +0 -0
  154. scmcp_shared/vector_db/decoupler.lance/data/b1c47dc6-450a-4dca-b465-badb6a3619c2.lance +0 -0
  155. scmcp_shared/vector_db/decoupler.lance/data/b21d183a-c51d-463b-932e-9beb2e0da9aa.lance +0 -0
  156. scmcp_shared/vector_db/decoupler.lance/data/b2598625-8683-4c33-be40-6f1a7555dc2c.lance +0 -0
  157. scmcp_shared/vector_db/decoupler.lance/data/b7297b39-1fc5-4886-be21-708b369bef59.lance +0 -0
  158. scmcp_shared/vector_db/decoupler.lance/data/b7d90085-5bd6-48f9-8a73-17eb7cf556fa.lance +0 -0
  159. scmcp_shared/vector_db/decoupler.lance/data/b988ceee-57fc-423b-8e67-5729086c6946.lance +0 -0
  160. scmcp_shared/vector_db/decoupler.lance/data/bfd68ea6-ddf4-44bb-aa87-2669f0462f7d.lance +0 -0
  161. scmcp_shared/vector_db/decoupler.lance/data/cb8ac58e-0e59-4391-95ab-6195e71b9625.lance +0 -0
  162. scmcp_shared/vector_db/decoupler.lance/data/d0f10434-afb5-49c5-9e77-d39a4f8cca94.lance +0 -0
  163. scmcp_shared/vector_db/decoupler.lance/data/d3f89fec-5795-4e11-8c15-bc2206a7fae2.lance +0 -0
  164. scmcp_shared/vector_db/decoupler.lance/data/e063c8a4-5ef1-4933-a049-b41049d9be5f.lance +0 -0
  165. scmcp_shared/vector_db/decoupler.lance/data/e2348b24-f290-4fe2-a6c8-e98009ae4c1b.lance +0 -0
  166. scmcp_shared/vector_db/decoupler.lance/data/e5d3f893-6763-40dc-9d02-04e2f56a4883.lance +0 -0
  167. {scmcp_shared-0.3.7.dist-info → scmcp_shared-0.5.0.dist-info}/METADATA +3 -1
  168. scmcp_shared-0.5.0.dist-info/RECORD +171 -0
  169. scmcp_shared/server/base.py +0 -148
  170. scmcp_shared-0.3.7.dist-info/RECORD +0 -21
  171. {scmcp_shared-0.3.7.dist-info → scmcp_shared-0.5.0.dist-info}/WHEEL +0 -0
  172. {scmcp_shared-0.3.7.dist-info → scmcp_shared-0.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,21 +1,21 @@
1
- import os
2
- import inspect
3
- from pathlib import Path
4
- import scanpy as sc
5
- from fastmcp import FastMCP , Context
6
1
  from fastmcp.exceptions import ToolError
7
2
  from fastmcp.tools.tool import Tool
8
- from ..schema.util import *
9
- from ..schema import AdataInfo
10
- from ..util import filter_args, forward_request, get_ads, generate_msg,add_op_log
11
- from .base import BaseMCP
3
+ from scmcp_shared.schema.preset.util import *
4
+ from scmcp_shared.schema.preset import AdataInfo
5
+ from scmcp_shared.util import forward_request, get_ads, add_op_log
6
+ from scmcp_shared.mcp_base import BaseMCP
12
7
 
13
8
 
14
9
  class ScanpyUtilMCP(BaseMCP):
15
- def __init__(self, include_tools: list = None, exclude_tools: list = None, AdataInfo = AdataInfo):
10
+ def __init__(
11
+ self,
12
+ include_tools: list = None,
13
+ exclude_tools: list = None,
14
+ AdataInfo=AdataInfo,
15
+ ):
16
16
  """
17
17
  Initialize ScanpyUtilMCP with optional tool filtering.
18
-
18
+
19
19
  Args:
20
20
  include_tools (list, optional): List of tool names to include. If None, all tools are included.
21
21
  exclude_tools (list, optional): List of tool names to exclude. If None, no tools are excluded.
@@ -23,21 +23,23 @@ class ScanpyUtilMCP(BaseMCP):
23
23
  """
24
24
  super().__init__("SCMCP-Util-Server", include_tools, exclude_tools, AdataInfo)
25
25
 
26
-
27
26
  def _tool_query_op_log(self):
28
- def _query_op_log(request: QueryOpLogParams, adinfo: self.AdataInfo=self.AdataInfo()):
27
+ def _query_op_log(
28
+ request: QueryOpLogParam, adinfo: self.AdataInfo = self.AdataInfo()
29
+ ):
29
30
  """Query the adata operation log"""
30
31
  adata = get_ads().get_adata(adinfo=adinfo)
31
32
  op_dic = adata.uns["operation"]["op"]
32
- opids = adata.uns["operation"]["opid"][-request.n:]
33
+ opids = adata.uns["operation"]["opid"][-request.n :]
33
34
  op_list = []
34
35
  for opid in opids:
35
36
  op_list.append(op_dic[opid])
36
37
  return op_list
37
- return Tool.from_function(_query_op_log, name="query_op_log")
38
+
39
+ return Tool.from_function(_query_op_log, name="query_op_log", enabled=True)
38
40
 
39
41
  def _tool_mark_var(self):
40
- def _mark_var(request: MarkVarParams, adinfo: self.AdataInfo=self.AdataInfo()):
42
+ def _mark_var(request: MarkVarParam, adinfo: self.AdataInfo = self.AdataInfo()):
41
43
  """
42
44
  Determine if each gene meets specific conditions and store results in adata.var as boolean values.
43
45
  For example: mitochondrion genes startswith MT-.
@@ -54,14 +56,20 @@ class ScanpyUtilMCP(BaseMCP):
54
56
  patterns = request.patterns
55
57
  if gene_class is not None:
56
58
  if gene_class == "mitochondrion":
57
- adata.var["mt"] = adata.var_names.str.startswith(('MT-', 'Mt','mt-'))
59
+ adata.var["mt"] = adata.var_names.str.startswith(
60
+ ("MT-", "Mt", "mt-")
61
+ )
58
62
  var_name = "mt"
59
63
  elif gene_class == "ribosomal":
60
- adata.var["ribo"] = adata.var_names.str.startswith(("RPS", "RPL", "Rps", "Rpl"))
64
+ adata.var["ribo"] = adata.var_names.str.startswith(
65
+ ("RPS", "RPL", "Rps", "Rpl")
66
+ )
61
67
  var_name = "ribo"
62
68
  elif gene_class == "hemoglobin":
63
- adata.var["hb"] = adata.var_names.str.contains("^HB[^(P)]", case=False)
64
- var_name = "hb"
69
+ adata.var["hb"] = adata.var_names.str.contains(
70
+ "^HB[^(P)]", case=False
71
+ )
72
+ var_name = "hb"
65
73
  elif pattern_type is not None and patterns is not None:
66
74
  if pattern_type == "startswith":
67
75
  adata.var[var_name] = adata.var_names.str.startswith(patterns)
@@ -70,25 +78,39 @@ class ScanpyUtilMCP(BaseMCP):
70
78
  elif pattern_type == "contains":
71
79
  adata.var[var_name] = adata.var_names.str.contains(patterns)
72
80
  else:
73
- raise ValueError(f"Did not support pattern_type: {pattern_type}")
81
+ raise ValueError(
82
+ f"Did not support pattern_type: {pattern_type}"
83
+ )
74
84
  else:
75
- raise ValueError(f"Please provide validated parameter")
76
-
77
- res = {var_name: adata.var[var_name].value_counts().to_dict(), "msg": f"add '{var_name}' column in adata.var"}
78
- func_kwargs = {"var_name": var_name, "gene_class": gene_class, "pattern_type": pattern_type, "patterns": patterns}
85
+ raise ValueError("Please provide validated parameter")
86
+
87
+ res = {
88
+ var_name: adata.var[var_name].value_counts().to_dict(),
89
+ "msg": f"add '{var_name}' column in adata.var",
90
+ }
91
+ func_kwargs = {
92
+ "var_name": var_name,
93
+ "gene_class": gene_class,
94
+ "pattern_type": pattern_type,
95
+ "patterns": patterns,
96
+ }
79
97
  add_op_log(adata, "mark_var", func_kwargs, adinfo)
80
98
  return res
81
99
  except ToolError as e:
82
100
  raise ToolError(e)
83
101
  except Exception as e:
84
- if hasattr(e, '__context__') and e.__context__:
102
+ if hasattr(e, "__context__") and e.__context__:
85
103
  raise ToolError(e.__context__)
86
104
  else:
87
105
  raise ToolError(e)
88
- return Tool.from_function(_mark_var, name="mark_var")
106
+
107
+ return Tool.from_function(_mark_var, name="mark_var", enabled=True)
89
108
 
90
109
  def _tool_list_var(self):
91
- def _list_var(request: ListVarParams=ListVarParams(), adinfo: self.AdataInfo=self.AdataInfo()):
110
+ def _list_var(
111
+ request: ListVarParam = ListVarParam(),
112
+ adinfo: self.AdataInfo = self.AdataInfo(),
113
+ ):
92
114
  """List key columns in adata.var. It should be called for checking when other tools need var key column names as input."""
93
115
  try:
94
116
  result = forward_request("ul_list_var", request, adinfo)
@@ -101,19 +123,20 @@ class ScanpyUtilMCP(BaseMCP):
101
123
  except ToolError as e:
102
124
  raise ToolError(e)
103
125
  except Exception as e:
104
- if hasattr(e, '__context__') and e.__context__:
126
+ if hasattr(e, "__context__") and e.__context__:
105
127
  raise ToolError(e.__context__)
106
128
  else:
107
129
  raise ToolError(e)
108
- return Tool.from_function(_list_var, name="list_var")
130
+
131
+ return Tool.from_function(_list_var, name="list_var", enabled=True)
109
132
 
110
133
  def _tool_list_obs(self):
111
- def _list_obs(request: ListObsParams, adinfo: self.AdataInfo=self.AdataInfo()):
134
+ def _list_obs(request: ListObsParam, adinfo: self.AdataInfo = self.AdataInfo()):
112
135
  """List key columns in adata.obs. It should be called before other tools need obs key column names input."""
113
136
  try:
114
137
  result = forward_request("ul_list_obs", request, adinfo)
115
138
  if result is not None:
116
- return result
139
+ return result
117
140
  adata = get_ads().get_adata(adinfo=adinfo)
118
141
  columns = list(adata.obs.columns)
119
142
  add_op_log(adata, "list_obs", {}, adinfo)
@@ -121,19 +144,22 @@ class ScanpyUtilMCP(BaseMCP):
121
144
  except ToolError as e:
122
145
  raise ToolError(e)
123
146
  except Exception as e:
124
- if hasattr(e, '__context__') and e.__context__:
147
+ if hasattr(e, "__context__") and e.__context__:
125
148
  raise ToolError(e.__context__)
126
149
  else:
127
150
  raise ToolError(e)
128
- return Tool.from_function(_list_obs, name="list_obs")
151
+
152
+ return Tool.from_function(_list_obs, name="list_obs", enabled=True)
129
153
 
130
154
  def _tool_check_var(self):
131
- def _check_var(request: VarNamesParams, adinfo: self.AdataInfo=self.AdataInfo()):
155
+ def _check_var(
156
+ request: VarNamesParam, adinfo: self.AdataInfo = self.AdataInfo()
157
+ ):
132
158
  """Check if genes/variables exist in adata.var_names. This tool should be called before gene expression visualizations or color by genes."""
133
159
  try:
134
160
  result = forward_request("ul_check_var", request, adinfo)
135
161
  if result is not None:
136
- return result
162
+ return result
137
163
  adata = get_ads().get_adata(adinfo=adinfo)
138
164
  var_names = request.var_names
139
165
  if adata.raw is not None:
@@ -146,14 +172,17 @@ class ScanpyUtilMCP(BaseMCP):
146
172
  except ToolError as e:
147
173
  raise ToolError(e)
148
174
  except Exception as e:
149
- if hasattr(e, '__context__') and e.__context__:
175
+ if hasattr(e, "__context__") and e.__context__:
150
176
  raise ToolError(e.__context__)
151
177
  else:
152
178
  raise ToolError(e)
153
- return Tool.from_function(_check_var, name="check_var")
179
+
180
+ return Tool.from_function(_check_var, name="check_var", enabled=True)
154
181
 
155
182
  def _tool_merge_adata(self):
156
- def _merge_adata(request: ConcatBaseParams, adinfo: self.AdataInfo=self.AdataInfo()):
183
+ def _merge_adata(
184
+ request: ConcatBaseParam, adinfo: self.AdataInfo = self.AdataInfo()
185
+ ):
157
186
  """Merge multiple adata objects."""
158
187
  try:
159
188
  result = forward_request("ul_merge_adata", request, adinfo)
@@ -161,96 +190,124 @@ class ScanpyUtilMCP(BaseMCP):
161
190
  return result
162
191
  ads = get_ads()
163
192
  adata = ads.get_adata(adinfo=adinfo)
164
- kwargs = {k: v for k, v in request.model_dump().items() if v is not None}
193
+ kwargs = {
194
+ k: v for k, v in request.model_dump().items() if v is not None
195
+ }
165
196
  merged_adata = adata.concat(ads.adata_dic, **kwargs)
166
197
  ads.adata_dic[dtype] = {}
167
198
  ads.active_id = "merged_adata"
168
199
  add_op_log(merged_adata, ad.concat, kwargs, adinfo)
169
200
  ads.adata_dic[ads.active_id] = merged_adata
170
- return {"status": "success", "message": "Successfully merged all AnnData objects"}
201
+ return {
202
+ "status": "success",
203
+ "message": "Successfully merged all AnnData objects",
204
+ }
171
205
  except ToolError as e:
172
206
  raise ToolError(e)
173
207
  except Exception as e:
174
- if hasattr(e, '__context__') and e.__context__:
208
+ if hasattr(e, "__context__") and e.__context__:
175
209
  raise ToolError(e.__context__)
176
210
  else:
177
211
  raise ToolError(e)
178
- return Tool.from_function(_merge_adata, name="merge_adata")
212
+
213
+ return Tool.from_function(_merge_adata, name="merge_adata", enabled=True)
179
214
 
180
215
  def _tool_set_dpt_iroot(self):
181
- def _set_dpt_iroot(request: DPTIROOTParams, adinfo: self.AdataInfo=self.AdataInfo()):
216
+ def _set_dpt_iroot(
217
+ request: DPTIROOTParam, adinfo: self.AdataInfo = self.AdataInfo()
218
+ ):
182
219
  """Set the iroot cell"""
183
220
  try:
184
221
  result = forward_request("ul_set_dpt_iroot", request, adinfo)
185
222
  if result is not None:
186
- return result
223
+ return result
187
224
  adata = get_ads().get_adata(adinfo=adinfo)
188
225
  diffmap_key = request.diffmap_key
189
226
  dimension = request.dimension
190
227
  direction = request.direction
191
228
  if diffmap_key not in adata.obsm:
192
- raise ValueError(f"Diffusion map key '{diffmap_key}' not found in adata.obsm")
229
+ raise ValueError(
230
+ f"Diffusion map key '{diffmap_key}' not found in adata.obsm"
231
+ )
193
232
  if direction == "min":
194
233
  adata.uns["iroot"] = adata.obsm[diffmap_key][:, dimension].argmin()
195
- else:
234
+ else:
196
235
  adata.uns["iroot"] = adata.obsm[diffmap_key][:, dimension].argmax()
197
-
198
- func_kwargs = {"diffmap_key": diffmap_key, "dimension": dimension, "direction": direction}
236
+
237
+ func_kwargs = {
238
+ "diffmap_key": diffmap_key,
239
+ "dimension": dimension,
240
+ "direction": direction,
241
+ }
199
242
  add_op_log(adata, "set_dpt_iroot", func_kwargs, adinfo)
200
-
201
- return {"status": "success", "message": f"Successfully set root cell for DPT using {direction} of dimension {dimension}"}
243
+
244
+ return {
245
+ "status": "success",
246
+ "message": f"Successfully set root cell for DPT using {direction} of dimension {dimension}",
247
+ }
202
248
  except ToolError as e:
203
249
  raise ToolError(e)
204
250
  except Exception as e:
205
- if hasattr(e, '__context__') and e.__context__:
251
+ if hasattr(e, "__context__") and e.__context__:
206
252
  raise ToolError(e.__context__)
207
253
  else:
208
254
  raise ToolError(e)
209
- return Tool.from_function(_set_dpt_iroot, name="set_dpt_iroot")
255
+
256
+ return Tool.from_function(_set_dpt_iroot, name="set_dpt_iroot", enabled=True)
210
257
 
211
258
  def _tool_add_layer(self):
212
- def _add_layer(request: AddLayerParams, adinfo: self.AdataInfo=self.AdataInfo()):
259
+ def _add_layer(
260
+ request: AddLayerParam, adinfo: self.AdataInfo = self.AdataInfo()
261
+ ):
213
262
  """Add a layer to the AnnData object."""
214
263
  try:
215
264
  result = forward_request("ul_add_layer", request, adinfo)
216
265
  if result is not None:
217
- return result
266
+ return result
218
267
  adata = get_ads().get_adata(adinfo=adinfo)
219
268
  layer_name = request.layer_name
220
-
269
+
221
270
  # Check if layer already exists
222
271
  if layer_name in adata.layers:
223
- raise ValueError(f"Layer '{layer_name}' already exists in adata.layers")
272
+ raise ValueError(
273
+ f"Layer '{layer_name}' already exists in adata.layers"
274
+ )
224
275
  # Add the data as a new layer
225
276
  adata.layers[layer_name] = adata.X.copy()
226
277
 
227
278
  func_kwargs = {"layer_name": layer_name}
228
279
  add_op_log(adata, "add_layer", func_kwargs, adinfo)
229
-
280
+
230
281
  return {
231
- "status": "success",
232
- "message": f"Successfully added layer '{layer_name}' to adata.layers"
282
+ "status": "success",
283
+ "message": f"Successfully added layer '{layer_name}' to adata.layers",
233
284
  }
234
285
  except ToolError as e:
235
286
  raise ToolError(e)
236
287
  except Exception as e:
237
- if hasattr(e, '__context__') and e.__context__:
288
+ if hasattr(e, "__context__") and e.__context__:
238
289
  raise ToolError(e.__context__)
239
290
  else:
240
291
  raise ToolError(e)
241
- return Tool.from_function(_add_layer, name="add_layer")
292
+
293
+ return Tool.from_function(_add_layer, name="add_layer", enabled=True)
242
294
 
243
295
  def _tool_check_samples(self):
244
- def _check_samples(request: None, adinfo: self.AdataInfo=self.AdataInfo()):
296
+ def _check_samples(request: None, adinfo: self.AdataInfo = self.AdataInfo()):
245
297
  """check the stored samples"""
246
298
  try:
247
299
  ads = get_ads()
248
- return {"sampleid": [list(ads.adata_dic[dk].keys()) for dk in ads.adata_dic.keys()]}
300
+ return {
301
+ "sampleid": [
302
+ list(ads.adata_dic[dk].keys()) for dk in ads.adata_dic.keys()
303
+ ]
304
+ }
249
305
  except ToolError as e:
250
306
  raise ToolError(e)
251
307
  except Exception as e:
252
- if hasattr(e, '__context__') and e.__context__:
308
+ if hasattr(e, "__context__") and e.__context__:
253
309
  raise ToolError(e.__context__)
254
310
  else:
255
311
  raise ToolError(e)
256
- return Tool.from_function(_check_samples, name="check_samples")
312
+
313
+ return Tool.from_function(_check_samples, name="check_samples", enabled=True)
scmcp_shared/util.py CHANGED
@@ -1,17 +1,17 @@
1
1
  import inspect
2
2
  import os
3
- from enum import Enum
4
3
  from pathlib import Path
5
4
  from fastmcp.server.dependencies import get_context
6
5
  from fastmcp.exceptions import ToolError
7
6
  import asyncio
8
7
  import nest_asyncio
8
+ from pydantic import BaseModel
9
9
 
10
10
 
11
11
  def get_env(key):
12
12
  return os.environ.get(f"SCMCP_{key.upper()}")
13
13
 
14
-
14
+
15
15
  def filter_args(request, func, **extra_kwargs):
16
16
  kwargs = request.model_dump()
17
17
  args = request.model_fields_set
@@ -25,7 +25,7 @@ def filter_args(request, func, **extra_kwargs):
25
25
  def add_op_log(adata, func, kwargs, adinfo):
26
26
  import hashlib
27
27
  import json
28
-
28
+
29
29
  if "operation" not in adata.uns:
30
30
  adata.uns["operation"] = {}
31
31
  adata.uns["operation"]["op"] = {}
@@ -41,26 +41,27 @@ def add_op_log(adata, func, kwargs, adinfo):
41
41
  else:
42
42
  func_name = str(func)
43
43
  new_kwargs = {**adinfo.model_dump()}
44
- for k,v in kwargs.items():
44
+ for k, v in kwargs.items():
45
45
  if isinstance(v, tuple):
46
46
  new_kwargs[k] = list(v)
47
47
  else:
48
48
  new_kwargs[k] = v
49
49
  try:
50
50
  kwargs_str = json.dumps(new_kwargs, sort_keys=True)
51
- except:
52
- kwargs_str = str(new_kwargs)
51
+ except Exception as e:
52
+ print(e)
53
+ kwargs_str = f"{e}" + str(new_kwargs)
53
54
  hash_input = f"{func_name}:{kwargs_str}"
54
55
  hash_key = hashlib.md5(hash_input.encode()).hexdigest()
55
56
  adata.uns["operation"]["op"][hash_key] = {func_name: new_kwargs}
56
57
  adata.uns["operation"]["opid"] = list(adata.uns["operation"]["opid"])
57
58
  adata.uns["operation"]["opid"].append(hash_key)
58
59
  from .logging_config import setup_logger
60
+
59
61
  logger = setup_logger(log_file=get_env("LOG_FILE"))
60
62
  logger.info(f"{func}: {new_kwargs}")
61
63
 
62
64
 
63
-
64
65
  def save_fig_path(axes, file):
65
66
  from matplotlib.axes import Axes
66
67
 
@@ -76,12 +77,14 @@ def save_fig_path(axes, file):
76
77
  ax.figure.savefig(file_path)
77
78
  elif isinstance(axes, Axes):
78
79
  axes.figure.savefig(file_path)
79
- elif hasattr(axes, 'savefig'): # if Figure
80
+ elif hasattr(axes, "savefig"): # if Figure
80
81
  axes.savefig(file_path)
81
- elif hasattr(axes, 'save'): # for plotnine.ggplot.ggplot
82
+ elif hasattr(axes, "save"): # for plotnine.ggplot.ggplot
82
83
  axes.save(file_path)
83
84
  else:
84
- raise ValueError(f"axes must be a Axes or plotnine object, but got {type(axes)}")
85
+ raise ValueError(
86
+ f"axes must be a Axes or plotnine object, but got {type(axes)}"
87
+ )
85
88
  return file_path
86
89
  except Exception as e:
87
90
  raise e
@@ -102,7 +105,7 @@ def savefig(axes, func=None, **kwargs):
102
105
  kwargs.pop("save", None)
103
106
  kwargs.pop("show", None)
104
107
  args = []
105
- for k,v in kwargs.items():
108
+ for k, v in kwargs.items():
106
109
  if isinstance(v, (tuple, list, set)):
107
110
  v = v[:3] ## show first 3 elements
108
111
  args.append(f"{k}-{'-'.join([str(i) for i in v])}")
@@ -119,7 +122,7 @@ def savefig(axes, func=None, **kwargs):
119
122
  raise PermissionError("You don't have permission to save figure")
120
123
  except Exception as e:
121
124
  raise e
122
- transport = get_env("TRANSPORT")
125
+ transport = get_env("TRANSPORT")
123
126
  if transport == "stdio":
124
127
  return fig_path
125
128
  else:
@@ -129,36 +132,41 @@ def savefig(axes, func=None, **kwargs):
129
132
  return fig_path
130
133
 
131
134
 
132
-
133
135
  async def get_figure(request):
134
136
  from starlette.responses import FileResponse, Response
135
137
 
136
138
  figure_name = request.path_params["figure_name"]
137
139
  figure_path = f"./figures/{figure_name}"
138
-
140
+
139
141
  if not os.path.isfile(figure_path):
140
- return Response(content={"error": "figure not found"}, media_type="application/json")
141
-
142
+ return Response(
143
+ content={"error": "figure not found"}, media_type="application/json"
144
+ )
145
+
142
146
  return FileResponse(figure_path)
143
147
 
144
148
 
145
149
  def add_figure_route(server):
146
150
  from starlette.routing import Route
147
- server._additional_http_routes = [Route("/figures/{figure_name}", endpoint=get_figure)]
151
+
152
+ server._additional_http_routes = [
153
+ Route("/figures/{figure_name}", endpoint=get_figure)
154
+ ]
148
155
 
149
156
 
150
157
  async def async_forward_request(func, request, adinfo, **kwargs):
151
158
  from fastmcp import Client
159
+
152
160
  forward_url = get_env("FORWARD")
153
161
  request_kwargs = request.model_dump()
154
162
  request_args = request.model_fields_set
155
163
  func_kwargs = {
156
164
  "request": {k: request_kwargs.get(k) for k in request_args},
157
- "adinfo": adinfo.model_dump()
165
+ "adinfo": adinfo.model_dump(),
158
166
  }
159
167
  if not forward_url:
160
168
  return None
161
-
169
+
162
170
  client = Client(forward_url)
163
171
  async with client:
164
172
  tools = await client.list_tools()
@@ -169,13 +177,12 @@ async def async_forward_request(func, request, adinfo, **kwargs):
169
177
  except ToolError as e:
170
178
  raise ToolError(e)
171
179
  except Exception as e:
172
- if hasattr(e, '__context__') and e.__context__:
180
+ if hasattr(e, "__context__") and e.__context__:
173
181
  raise Exception(f"{str(e.__context__)}")
174
182
  else:
175
183
  raise e
176
184
 
177
185
 
178
-
179
186
  def forward_request(func, request, adinfo, **kwargs):
180
187
  """Synchronous wrapper for forward_request"""
181
188
  try:
@@ -186,12 +193,13 @@ def forward_request(func, request, adinfo, **kwargs):
186
193
  # If we're in a running event loop, use create_task
187
194
  async def _run():
188
195
  return await async_forward_request(func, request, adinfo, **kwargs)
196
+
189
197
  return loop.run_until_complete(_run())
190
198
  else:
191
199
  # If no event loop is running, use asyncio.run()
192
200
  return asyncio.run(async_forward_request(func, request, adinfo, **kwargs))
193
201
  except Exception as e:
194
- if hasattr(e, '__context__') and e.__context__:
202
+ if hasattr(e, "__context__") and e.__context__:
195
203
  raise Exception(f"{str(e.__context__)}")
196
204
  else:
197
205
  raise e
@@ -203,7 +211,9 @@ def obsm2adata(adata, obsm_key):
203
211
  if obsm_key not in adata.obsm_keys():
204
212
  raise ValueError(f"key {obsm_key} not found in adata.obsm")
205
213
  else:
206
- return AnnData(adata.obsm[obsm_key], obs=adata.obs, obsm=adata.obsm, uns=adata.uns)
214
+ return AnnData(
215
+ adata.obsm[obsm_key], obs=adata.obs, obsm=adata.obsm, uns=adata.uns
216
+ )
207
217
 
208
218
 
209
219
  def get_ads():
@@ -213,7 +223,11 @@ def get_ads():
213
223
 
214
224
 
215
225
  def generate_msg(adinfo, adata, ads):
216
- return {"sampleid": adinfo.sampleid or ads.active_id, "adtype": adinfo.adtype, "adata": adata}
226
+ return {
227
+ "sampleid": adinfo.sampleid or ads.active_id,
228
+ "adtype": adinfo.adtype,
229
+ "adata": adata,
230
+ }
217
231
 
218
232
 
219
233
  def sc_like_plot(plot_func, adata, request, adinfo, **kwargs):
@@ -231,7 +245,9 @@ def sc_like_plot(plot_func, adata, request, adinfo, **kwargs):
231
245
  def filter_tools(mcp, include_tools=None, exclude_tools=None):
232
246
  import asyncio
233
247
  import copy
248
+
234
249
  mcp = copy.deepcopy(mcp)
250
+
235
251
  async def _filter_tools(mcp, include_tools=None, exclude_tools=None):
236
252
  tools = await mcp.get_tools()
237
253
  for tool in tools:
@@ -240,43 +256,98 @@ def filter_tools(mcp, include_tools=None, exclude_tools=None):
240
256
  if include_tools and tool not in include_tools:
241
257
  mcp.remove_tool(tool)
242
258
  return mcp
259
+
243
260
  return asyncio.run(_filter_tools(mcp, include_tools, exclude_tools))
244
261
 
245
262
 
246
-
247
263
  def set_env(log_file, forward, transport, host, port):
248
264
  if log_file is not None:
249
- os.environ['SCMCP_LOG_FILE'] = log_file
265
+ os.environ["SCMCP_LOG_FILE"] = log_file
250
266
  if forward is not None:
251
- os.environ['SCMCP_FORWARD'] = forward
252
- os.environ['SCMCP_TRANSPORT'] = transport
253
- os.environ['SCMCP_HOST'] = host
254
- os.environ['SCMCP_PORT'] = str(port)
255
-
267
+ os.environ["SCMCP_FORWARD"] = forward
268
+ os.environ["SCMCP_TRANSPORT"] = transport
269
+ os.environ["SCMCP_HOST"] = host
270
+ os.environ["SCMCP_PORT"] = str(port)
256
271
 
257
272
 
258
273
  def setup_mcp(mcp, sub_mcp_dic, modules=None):
259
274
  import asyncio
275
+
260
276
  if modules is None or modules == "all":
261
277
  modules = sub_mcp_dic.keys()
262
278
  for module in modules:
263
279
  asyncio.run(mcp.import_server(module, sub_mcp_dic[module]))
264
280
  return mcp
265
281
 
266
- def _update_args(mcp, func, args_dic : dict):
282
+
283
+ def _update_args(mcp, func, args_dic: dict):
267
284
  for args, property_dic in args_dic.items():
268
285
  for pk, v in property_dic.items():
269
- mcp._tool_manager._tools[func].parameters["properties"]["request"].setdefault(pk, {})
270
- mcp._tool_manager._tools[func].parameters["properties"]["request"][pk][args] = v
286
+ mcp._tool_manager._tools[func].parameters["properties"][
287
+ "request"
288
+ ].setdefault(pk, {})
289
+ mcp._tool_manager._tools[func].parameters["properties"]["request"][pk][
290
+ args
291
+ ] = v
271
292
 
272
293
 
273
- def update_mcp_args(mcp, tool_args : dict):
274
- tools = mcp._tool_manager._tools.keys()
275
- for tool in tool_args:
294
+ def update_mcp_args(mcp, tool_args: dict):
295
+ # tools = mcp._tool_manager._tools.keys()
296
+ for tool in tool_args:
276
297
  _update_args(mcp, tool, tool_args[tool])
277
298
 
278
299
 
279
300
  def check_adata(adata, adinfo, ads):
280
301
  sampleid = adinfo.sampleid or ads.active_id
281
302
  if sampleid != adata.uns["scmcp_sampleid"]:
282
- raise ValueError(f"sampleid mismatch: {sampleid} != {adata.uns['scmcp_sampleid']}")
303
+ raise ValueError(
304
+ f"sampleid mismatch: {sampleid} != {adata.uns['scmcp_sampleid']}"
305
+ )
306
+
307
+
308
+ def get_nbm():
309
+ ctx = get_context()
310
+ nbm = ctx.request_context.lifespan_context
311
+ return nbm
312
+
313
+
314
+ def parse_args(
315
+ kwargs: BaseModel | dict,
316
+ positional_args: list[str] | str | None = None,
317
+ func_args: list[str] | str | None = None,
318
+ ) -> str:
319
+ if isinstance(kwargs, BaseModel):
320
+ kwargs = kwargs.model_dump()
321
+ elif isinstance(kwargs, dict):
322
+ kwargs = kwargs
323
+ else:
324
+ raise ValueError(f"Invalid type: {type(kwargs)}")
325
+
326
+ if func_args is not None:
327
+ if isinstance(func_args, str):
328
+ func_args = [func_args]
329
+ else:
330
+ func_args = []
331
+ kwargs_str_ls = []
332
+ if positional_args is not None:
333
+ if isinstance(positional_args, str):
334
+ kwargs_str_ls.append(kwargs.pop(positional_args))
335
+ elif isinstance(positional_args, list):
336
+ for arg in positional_args:
337
+ kwargs_str_ls.append(kwargs.pop(arg))
338
+
339
+ extra_kwargs = kwargs.pop("kwargs", {})
340
+ for k, v in kwargs.items():
341
+ if k in func_args:
342
+ kwargs_str_ls.append(f"{k}={v}")
343
+ continue
344
+ if isinstance(v, (list, tuple, dict, int, float, bool)):
345
+ kwargs_str_ls.append(f"{k}={v}")
346
+ elif isinstance(v, str):
347
+ kwargs_str_ls.append(f"{k}='{v}'")
348
+
349
+ if extra_kwargs:
350
+ kwargs_str_ls.append(f"**{extra_kwargs}")
351
+
352
+ kwargs_str = ", ".join(kwargs_str_ls)
353
+ return kwargs_str