napistu 0.2.5.dev6__py3-none-any.whl → 0.3.1__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 (107) hide show
  1. napistu/__main__.py +126 -96
  2. napistu/constants.py +35 -41
  3. napistu/context/__init__.py +10 -0
  4. napistu/context/discretize.py +462 -0
  5. napistu/context/filtering.py +387 -0
  6. napistu/gcs/__init__.py +1 -1
  7. napistu/identifiers.py +74 -15
  8. napistu/indices.py +68 -0
  9. napistu/ingestion/__init__.py +1 -1
  10. napistu/ingestion/bigg.py +47 -62
  11. napistu/ingestion/constants.py +18 -133
  12. napistu/ingestion/gtex.py +113 -0
  13. napistu/ingestion/hpa.py +147 -0
  14. napistu/ingestion/sbml.py +0 -97
  15. napistu/ingestion/string.py +2 -2
  16. napistu/matching/__init__.py +10 -0
  17. napistu/matching/constants.py +18 -0
  18. napistu/matching/interactions.py +518 -0
  19. napistu/matching/mount.py +529 -0
  20. napistu/matching/species.py +510 -0
  21. napistu/mcp/__init__.py +7 -4
  22. napistu/mcp/__main__.py +128 -72
  23. napistu/mcp/client.py +16 -25
  24. napistu/mcp/codebase.py +201 -153
  25. napistu/mcp/component_base.py +170 -0
  26. napistu/mcp/config.py +223 -0
  27. napistu/mcp/constants.py +45 -2
  28. napistu/mcp/documentation.py +253 -136
  29. napistu/mcp/documentation_utils.py +13 -48
  30. napistu/mcp/execution.py +372 -305
  31. napistu/mcp/health.py +49 -67
  32. napistu/mcp/profiles.py +10 -6
  33. napistu/mcp/server.py +161 -80
  34. napistu/mcp/tutorials.py +139 -87
  35. napistu/modify/__init__.py +1 -1
  36. napistu/modify/gaps.py +1 -1
  37. napistu/network/__init__.py +1 -1
  38. napistu/network/constants.py +101 -34
  39. napistu/network/data_handling.py +388 -0
  40. napistu/network/ig_utils.py +351 -0
  41. napistu/network/napistu_graph_core.py +354 -0
  42. napistu/network/neighborhoods.py +40 -40
  43. napistu/network/net_create.py +373 -309
  44. napistu/network/net_propagation.py +47 -19
  45. napistu/network/{net_utils.py → ng_utils.py} +124 -272
  46. napistu/network/paths.py +67 -51
  47. napistu/network/precompute.py +11 -11
  48. napistu/ontologies/__init__.py +10 -0
  49. napistu/ontologies/constants.py +129 -0
  50. napistu/ontologies/dogma.py +243 -0
  51. napistu/ontologies/genodexito.py +649 -0
  52. napistu/ontologies/mygene.py +369 -0
  53. napistu/ontologies/renaming.py +198 -0
  54. napistu/rpy2/__init__.py +229 -86
  55. napistu/rpy2/callr.py +47 -77
  56. napistu/rpy2/constants.py +24 -23
  57. napistu/rpy2/rids.py +61 -648
  58. napistu/sbml_dfs_core.py +587 -222
  59. napistu/scverse/__init__.py +15 -0
  60. napistu/scverse/constants.py +28 -0
  61. napistu/scverse/loading.py +727 -0
  62. napistu/utils.py +118 -10
  63. {napistu-0.2.5.dev6.dist-info → napistu-0.3.1.dist-info}/METADATA +8 -3
  64. napistu-0.3.1.dist-info/RECORD +133 -0
  65. tests/conftest.py +22 -0
  66. tests/test_context_discretize.py +56 -0
  67. tests/test_context_filtering.py +267 -0
  68. tests/test_identifiers.py +100 -0
  69. tests/test_indices.py +65 -0
  70. tests/{test_edgelist.py → test_ingestion_napistu_edgelist.py} +2 -2
  71. tests/test_matching_interactions.py +108 -0
  72. tests/test_matching_mount.py +305 -0
  73. tests/test_matching_species.py +394 -0
  74. tests/test_mcp_config.py +193 -0
  75. tests/test_mcp_documentation_utils.py +12 -3
  76. tests/test_mcp_server.py +356 -0
  77. tests/test_network_data_handling.py +397 -0
  78. tests/test_network_ig_utils.py +23 -0
  79. tests/test_network_neighborhoods.py +19 -0
  80. tests/test_network_net_create.py +459 -0
  81. tests/test_network_ng_utils.py +30 -0
  82. tests/test_network_paths.py +56 -0
  83. tests/{test_precomputed_distances.py → test_network_precompute.py} +8 -6
  84. tests/test_ontologies_genodexito.py +58 -0
  85. tests/test_ontologies_mygene.py +39 -0
  86. tests/test_ontologies_renaming.py +110 -0
  87. tests/test_rpy2_callr.py +79 -0
  88. tests/test_rpy2_init.py +151 -0
  89. tests/test_sbml.py +0 -31
  90. tests/test_sbml_dfs_core.py +134 -10
  91. tests/test_scverse_loading.py +778 -0
  92. tests/test_set_coverage.py +2 -2
  93. tests/test_utils.py +121 -1
  94. napistu/mechanism_matching.py +0 -1353
  95. napistu/rpy2/netcontextr.py +0 -467
  96. napistu-0.2.5.dev6.dist-info/RECORD +0 -97
  97. tests/test_igraph.py +0 -367
  98. tests/test_mechanism_matching.py +0 -784
  99. tests/test_net_utils.py +0 -149
  100. tests/test_netcontextr.py +0 -105
  101. tests/test_rpy2.py +0 -61
  102. /napistu/ingestion/{cpr_edgelist.py → napistu_edgelist.py} +0 -0
  103. {napistu-0.2.5.dev6.dist-info → napistu-0.3.1.dist-info}/WHEEL +0 -0
  104. {napistu-0.2.5.dev6.dist-info → napistu-0.3.1.dist-info}/entry_points.txt +0 -0
  105. {napistu-0.2.5.dev6.dist-info → napistu-0.3.1.dist-info}/licenses/LICENSE +0 -0
  106. {napistu-0.2.5.dev6.dist-info → napistu-0.3.1.dist-info}/top_level.txt +0 -0
  107. /tests/{test_obo.py → test_ingestion_obo.py} +0 -0
napistu/mcp/codebase.py CHANGED
@@ -2,181 +2,229 @@
2
2
  Codebase exploration components for the Napistu MCP server.
3
3
  """
4
4
 
5
- from napistu.mcp.constants import NAPISTU_PY_READTHEDOCS_API
6
-
7
- from fastmcp import FastMCP
8
-
9
5
  from typing import Dict, Any
10
6
  import json
7
+ import logging
11
8
 
9
+ from fastmcp import FastMCP
10
+
11
+ from napistu.mcp.component_base import ComponentState, MCPComponent
12
+ from napistu.mcp.constants import NAPISTU_PY_READTHEDOCS_API
12
13
  from napistu.mcp import codebase_utils
13
14
  from napistu.mcp import utils as mcp_utils
14
15
 
15
- # Global cache for codebase information
16
- _codebase_cache = {
17
- "modules": {},
18
- "classes": {},
19
- "functions": {},
20
- }
16
+ logger = logging.getLogger(__name__)
21
17
 
22
18
 
23
- async def initialize_components() -> bool:
24
- """
25
- Initialize codebase components.
19
+ class CodebaseState(ComponentState):
20
+ """State management for codebase component."""
26
21
 
27
- Returns
28
- -------
29
- bool
30
- True if initialization is successful.
31
- """
32
- global _codebase_cache
33
- # Load documentation from the ReadTheDocs API
34
- _codebase_cache["modules"] = await codebase_utils.read_read_the_docs(
35
- NAPISTU_PY_READTHEDOCS_API
36
- )
37
- # Extract functions and classes from the modules
38
- _codebase_cache["functions"], _codebase_cache["classes"] = (
39
- codebase_utils.extract_functions_and_classes_from_modules(
40
- _codebase_cache["modules"]
41
- )
42
- )
43
- return True
44
-
45
-
46
- def register_components(mcp: FastMCP):
47
- """
48
- Register codebase exploration components with the MCP server.
22
+ def __init__(self):
23
+ super().__init__()
24
+ self.codebase_cache: Dict[str, Dict[str, Any]] = {
25
+ "modules": {},
26
+ "classes": {},
27
+ "functions": {},
28
+ }
49
29
 
50
- Args:
51
- mcp: FastMCP server instance
52
- """
53
- global _codebase_cache
30
+ def is_healthy(self) -> bool:
31
+ """Component is healthy if it has loaded any codebase information."""
32
+ return any(bool(section) for section in self.codebase_cache.values())
54
33
 
55
- # Register resources
56
- @mcp.resource("napistu://codebase/summary")
57
- async def get_codebase_summary() -> Dict[str, Any]:
58
- """
59
- Get a summary of the Napistu codebase structure.
60
- """
34
+ def get_health_details(self) -> Dict[str, Any]:
35
+ """Provide codebase-specific health details."""
61
36
  return {
62
- "modules": list(_codebase_cache["modules"].keys()),
63
- "top_level_classes": [
64
- class_name
65
- for class_name, info in _codebase_cache["classes"].items()
66
- if "." not in class_name # Only include top-level classes
67
- ],
68
- "top_level_functions": [
69
- func_name
70
- for func_name, info in _codebase_cache["functions"].items()
71
- if "." not in func_name # Only include top-level functions
72
- ],
37
+ "modules_count": len(self.codebase_cache["modules"]),
38
+ "classes_count": len(self.codebase_cache["classes"]),
39
+ "functions_count": len(self.codebase_cache["functions"]),
40
+ "total_items": sum(
41
+ len(section) for section in self.codebase_cache.values()
42
+ ),
73
43
  }
74
44
 
75
- @mcp.resource("napistu://codebase/modules/{module_name}")
76
- async def get_module_details(module_name: str) -> Dict[str, Any]:
77
- """
78
- Get detailed information about a specific module.
79
45
 
80
- Args:
81
- module_name: Name of the module
82
- """
83
- if module_name not in _codebase_cache["modules"]:
84
- return {"error": f"Module {module_name} not found"}
46
+ class CodebaseComponent(MCPComponent):
47
+ """MCP component for codebase exploration and search."""
85
48
 
86
- return _codebase_cache["modules"][module_name]
49
+ def _create_state(self) -> CodebaseState:
50
+ """Create codebase-specific state."""
51
+ return CodebaseState()
87
52
 
88
- # Register tools
89
- @mcp.tool()
90
- async def search_codebase(query: str) -> Dict[str, Any]:
53
+ async def initialize(self) -> bool:
91
54
  """
92
- Search the codebase for a specific query.
55
+ Initialize codebase component by loading documentation from ReadTheDocs.
93
56
 
94
- Args:
95
- query: Search term
96
-
97
- Returns:
98
- Dictionary with search results organized by code element type, including snippets for context.
57
+ Returns
58
+ -------
59
+ bool
60
+ True if codebase information was loaded successfully
99
61
  """
100
- results = {
101
- "modules": [],
102
- "classes": [],
103
- "functions": [],
104
- }
105
-
106
- # Search modules
107
- for module_name, info in _codebase_cache["modules"].items():
108
- # Use docstring or description for snippet
109
- doc = info.get("doc") or info.get("description") or ""
110
- module_text = json.dumps(info)
111
- if query.lower() in module_text.lower():
112
- snippet = mcp_utils.get_snippet(doc, query)
113
- results["modules"].append(
114
- {
115
- "name": module_name,
116
- "description": doc,
117
- "snippet": snippet,
118
- }
119
- )
120
-
121
- # Search classes
122
- for class_name, info in _codebase_cache["classes"].items():
123
- doc = info.get("doc") or info.get("description") or ""
124
- class_text = json.dumps(info)
125
- if query.lower() in class_text.lower():
126
- snippet = mcp_utils.get_snippet(doc, query)
127
- results["classes"].append(
128
- {
129
- "name": class_name,
130
- "description": doc,
131
- "snippet": snippet,
132
- }
133
- )
134
-
135
- # Search functions
136
- for func_name, info in _codebase_cache["functions"].items():
137
- doc = info.get("doc") or info.get("description") or ""
138
- func_text = json.dumps(info)
139
- if query.lower() in func_text.lower():
140
- snippet = mcp_utils.get_snippet(doc, query)
141
- results["functions"].append(
142
- {
143
- "name": func_name,
144
- "description": doc,
145
- "signature": info.get("signature", ""),
146
- "snippet": snippet,
147
- }
148
- )
149
-
150
- return results
151
-
152
- @mcp.tool()
153
- async def get_function_documentation(function_name: str) -> Dict[str, Any]:
62
+ try:
63
+ logger.info("Loading codebase documentation from ReadTheDocs...")
64
+
65
+ # Load documentation from the ReadTheDocs API
66
+ modules = await codebase_utils.read_read_the_docs(
67
+ NAPISTU_PY_READTHEDOCS_API
68
+ )
69
+ self.state.codebase_cache["modules"] = modules
70
+
71
+ # Extract functions and classes from the modules
72
+ functions, classes = (
73
+ codebase_utils.extract_functions_and_classes_from_modules(modules)
74
+ )
75
+ self.state.codebase_cache["functions"] = functions
76
+ self.state.codebase_cache["classes"] = classes
77
+
78
+ logger.info(
79
+ f"Codebase loading complete: "
80
+ f"{len(modules)} modules, "
81
+ f"{len(classes)} classes, "
82
+ f"{len(functions)} functions"
83
+ )
84
+
85
+ # Consider successful if we loaded any modules
86
+ return len(modules) > 0
87
+
88
+ except Exception as e:
89
+ logger.error(f"Failed to load codebase documentation: {e}")
90
+ return False
91
+
92
+ def register(self, mcp: FastMCP) -> None:
154
93
  """
155
- Get detailed documentation for a specific function.
156
-
157
- Args:
158
- function_name: Name of the function
94
+ Register codebase resources and tools with the MCP server.
159
95
 
160
- Returns:
161
- Dictionary with function documentation
96
+ Parameters
97
+ ----------
98
+ mcp : FastMCP
99
+ FastMCP server instance
162
100
  """
163
- if function_name not in _codebase_cache["functions"]:
164
- return {"error": f"Function {function_name} not found"}
165
101
 
166
- return _codebase_cache["functions"][function_name]
167
-
168
- @mcp.tool()
169
- async def get_class_documentation(class_name: str) -> Dict[str, Any]:
170
- """
171
- Get detailed documentation for a specific class.
172
-
173
- Args:
174
- class_name: Name of the class
175
-
176
- Returns:
177
- Dictionary with class documentation
178
- """
179
- if class_name not in _codebase_cache["classes"]:
180
- return {"error": f"Class {class_name} not found"}
102
+ # Register resources
103
+ @mcp.resource("napistu://codebase/summary")
104
+ async def get_codebase_summary():
105
+ """Get a summary of all available codebase information."""
106
+ return {
107
+ "modules": list(self.state.codebase_cache["modules"].keys()),
108
+ "classes": list(self.state.codebase_cache["classes"].keys()),
109
+ "functions": list(self.state.codebase_cache["functions"].keys()),
110
+ }
111
+
112
+ @mcp.resource("napistu://codebase/modules/{module_name}")
113
+ async def get_module_details(module_name: str) -> Dict[str, Any]:
114
+ """Get detailed information about a specific module."""
115
+ if module_name not in self.state.codebase_cache["modules"]:
116
+ return {"error": f"Module {module_name} not found"}
117
+
118
+ return self.state.codebase_cache["modules"][module_name]
119
+
120
+ # Register tools
121
+ @mcp.tool()
122
+ async def search_codebase(query: str) -> Dict[str, Any]:
123
+ """
124
+ Search the codebase for a specific query.
125
+
126
+ Args:
127
+ query: Search term
128
+
129
+ Returns:
130
+ Dictionary with search results organized by code element type, including snippets for context.
131
+ """
132
+ results = {
133
+ "modules": [],
134
+ "classes": [],
135
+ "functions": [],
136
+ }
137
+
138
+ # Search modules
139
+ for module_name, info in self.state.codebase_cache["modules"].items():
140
+ # Use docstring or description for snippet
141
+ doc = info.get("doc") or info.get("description") or ""
142
+ module_text = json.dumps(info)
143
+ if query.lower() in module_text.lower():
144
+ snippet = mcp_utils.get_snippet(doc, query)
145
+ results["modules"].append(
146
+ {
147
+ "name": module_name,
148
+ "description": doc,
149
+ "snippet": snippet,
150
+ }
151
+ )
152
+
153
+ # Search classes
154
+ for class_name, info in self.state.codebase_cache["classes"].items():
155
+ doc = info.get("doc") or info.get("description") or ""
156
+ class_text = json.dumps(info)
157
+ if query.lower() in class_text.lower():
158
+ snippet = mcp_utils.get_snippet(doc, query)
159
+ results["classes"].append(
160
+ {
161
+ "name": class_name,
162
+ "description": doc,
163
+ "snippet": snippet,
164
+ }
165
+ )
166
+
167
+ # Search functions
168
+ for func_name, info in self.state.codebase_cache["functions"].items():
169
+ doc = info.get("doc") or info.get("description") or ""
170
+ func_text = json.dumps(info)
171
+ if query.lower() in func_text.lower():
172
+ snippet = mcp_utils.get_snippet(doc, query)
173
+ results["functions"].append(
174
+ {
175
+ "name": func_name,
176
+ "description": doc,
177
+ "signature": info.get("signature", ""),
178
+ "snippet": snippet,
179
+ }
180
+ )
181
+
182
+ return results
183
+
184
+ @mcp.tool()
185
+ async def get_function_documentation(function_name: str) -> Dict[str, Any]:
186
+ """
187
+ Get detailed documentation for a specific function.
188
+
189
+ Args:
190
+ function_name: Name of the function
191
+
192
+ Returns:
193
+ Dictionary with function documentation
194
+ """
195
+ if function_name not in self.state.codebase_cache["functions"]:
196
+ return {"error": f"Function {function_name} not found"}
197
+
198
+ return self.state.codebase_cache["functions"][function_name]
199
+
200
+ @mcp.tool()
201
+ async def get_class_documentation(class_name: str) -> Dict[str, Any]:
202
+ """
203
+ Get detailed documentation for a specific class.
204
+
205
+ Args:
206
+ class_name: Name of the class
207
+
208
+ Returns:
209
+ Dictionary with class documentation
210
+ """
211
+ if class_name not in self.state.codebase_cache["classes"]:
212
+ return {"error": f"Class {class_name} not found"}
213
+
214
+ return self.state.codebase_cache["classes"][class_name]
215
+
216
+
217
+ # Module-level component instance
218
+ _component = CodebaseComponent()
219
+
220
+
221
+ def get_component() -> CodebaseComponent:
222
+ """
223
+ Get the codebase component instance.
181
224
 
182
- return _codebase_cache["classes"][class_name]
225
+ Returns
226
+ -------
227
+ CodebaseComponent
228
+ The codebase component instance
229
+ """
230
+ return _component
@@ -0,0 +1,170 @@
1
+ """
2
+ Base classes for MCP server components.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Dict, Any
7
+ import logging
8
+
9
+ from fastmcp import FastMCP
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class ComponentState(ABC):
15
+ """
16
+ Base class for component state management.
17
+
18
+ Provides standard interface for health checking and initialization tracking.
19
+ """
20
+
21
+ def __init__(self):
22
+ self.initialized = False
23
+ self.initialization_error = None
24
+
25
+ @abstractmethod
26
+ def is_healthy(self) -> bool:
27
+ """
28
+ Check if component has successfully loaded data and is functioning.
29
+
30
+ Returns
31
+ -------
32
+ bool
33
+ True if component is healthy (has data and no errors)
34
+ """
35
+ pass
36
+
37
+ def is_available(self) -> bool:
38
+ """
39
+ Check if component is available (initialized without critical errors).
40
+
41
+ Returns
42
+ -------
43
+ bool
44
+ True if component initialized successfully, regardless of data status
45
+ """
46
+ return self.initialized and self.initialization_error is None
47
+
48
+ def get_health_status(self) -> Dict[str, Any]:
49
+ """
50
+ Get standardized health status for health checks.
51
+
52
+ Returns
53
+ -------
54
+ Dict[str, Any]
55
+ Health status dictionary with standard format:
56
+ - status: 'initializing', 'unavailable', 'inactive', or 'healthy'
57
+ - error: error message if unavailable
58
+ - additional component-specific details if healthy
59
+ """
60
+ if not self.initialized:
61
+ return {"status": "initializing"}
62
+ elif self.initialization_error:
63
+ return {"status": "unavailable", "error": str(self.initialization_error)}
64
+ elif not self.is_healthy():
65
+ return {"status": "inactive"}
66
+ else:
67
+ return {"status": "healthy", **self.get_health_details()}
68
+
69
+ def get_health_details(self) -> Dict[str, Any]:
70
+ """
71
+ Get component-specific health details.
72
+
73
+ Override in subclasses to provide additional health information.
74
+
75
+ Returns
76
+ -------
77
+ Dict[str, Any]
78
+ Component-specific health details
79
+ """
80
+ return {}
81
+
82
+
83
+ class MCPComponent(ABC):
84
+ """
85
+ Base class for MCP server components.
86
+
87
+ Provides standard interface for initialization and registration.
88
+ """
89
+
90
+ def __init__(self):
91
+ self.state = self._create_state()
92
+
93
+ @abstractmethod
94
+ def _create_state(self) -> ComponentState:
95
+ """
96
+ Create the component state object.
97
+
98
+ Returns
99
+ -------
100
+ ComponentState
101
+ Component-specific state instance
102
+ """
103
+ pass
104
+
105
+ @abstractmethod
106
+ async def initialize(self) -> bool:
107
+ """
108
+ Initialize the component asynchronously.
109
+
110
+ Should populate component state and handle any external data loading.
111
+
112
+ Returns
113
+ -------
114
+ bool
115
+ True if initialization successful
116
+ """
117
+ pass
118
+
119
+ @abstractmethod
120
+ def register(self, mcp: FastMCP) -> None:
121
+ """
122
+ Register component resources and tools with the MCP server.
123
+
124
+ Parameters
125
+ ----------
126
+ mcp : FastMCP
127
+ FastMCP server instance to register with
128
+ """
129
+ pass
130
+
131
+ def get_state(self) -> ComponentState:
132
+ """
133
+ Get the component state for health checks and testing.
134
+
135
+ Returns
136
+ -------
137
+ ComponentState
138
+ Current component state
139
+ """
140
+ return self.state
141
+
142
+ async def safe_initialize(self) -> bool:
143
+ """
144
+ Initialize with error handling and state tracking.
145
+
146
+ Returns
147
+ -------
148
+ bool
149
+ True if initialization successful
150
+ """
151
+ try:
152
+ logger.info(f"Initializing {self.__class__.__name__}...")
153
+
154
+ result = await self.initialize()
155
+
156
+ self.state.initialized = True
157
+ self.state.initialization_error = None
158
+
159
+ if result:
160
+ logger.info(f"✅ {self.__class__.__name__} initialized successfully")
161
+ else:
162
+ logger.warning(f"⚠️ {self.__class__.__name__} initialized with issues")
163
+
164
+ return result
165
+
166
+ except Exception as e:
167
+ logger.error(f"❌ {self.__class__.__name__} failed to initialize: {e}")
168
+ self.state.initialized = True
169
+ self.state.initialization_error = e
170
+ return False