napistu 0.2.5.dev7__py3-none-any.whl → 0.3.1.dev1__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 (108) hide show
  1. napistu/__init__.py +1 -3
  2. napistu/__main__.py +126 -96
  3. napistu/constants.py +35 -41
  4. napistu/context/__init__.py +10 -0
  5. napistu/context/discretize.py +462 -0
  6. napistu/context/filtering.py +387 -0
  7. napistu/gcs/__init__.py +1 -1
  8. napistu/identifiers.py +74 -15
  9. napistu/indices.py +68 -0
  10. napistu/ingestion/__init__.py +1 -1
  11. napistu/ingestion/bigg.py +47 -62
  12. napistu/ingestion/constants.py +18 -133
  13. napistu/ingestion/gtex.py +113 -0
  14. napistu/ingestion/hpa.py +147 -0
  15. napistu/ingestion/sbml.py +0 -97
  16. napistu/ingestion/string.py +2 -2
  17. napistu/matching/__init__.py +10 -0
  18. napistu/matching/constants.py +18 -0
  19. napistu/matching/interactions.py +518 -0
  20. napistu/matching/mount.py +529 -0
  21. napistu/matching/species.py +510 -0
  22. napistu/mcp/__init__.py +7 -4
  23. napistu/mcp/__main__.py +128 -72
  24. napistu/mcp/client.py +16 -25
  25. napistu/mcp/codebase.py +201 -145
  26. napistu/mcp/component_base.py +170 -0
  27. napistu/mcp/config.py +223 -0
  28. napistu/mcp/constants.py +45 -2
  29. napistu/mcp/documentation.py +253 -136
  30. napistu/mcp/documentation_utils.py +13 -48
  31. napistu/mcp/execution.py +372 -305
  32. napistu/mcp/health.py +47 -65
  33. napistu/mcp/profiles.py +10 -6
  34. napistu/mcp/server.py +161 -80
  35. napistu/mcp/tutorials.py +139 -87
  36. napistu/modify/__init__.py +1 -1
  37. napistu/modify/gaps.py +1 -1
  38. napistu/network/__init__.py +1 -1
  39. napistu/network/constants.py +101 -34
  40. napistu/network/data_handling.py +388 -0
  41. napistu/network/ig_utils.py +351 -0
  42. napistu/network/napistu_graph_core.py +354 -0
  43. napistu/network/neighborhoods.py +40 -40
  44. napistu/network/net_create.py +373 -309
  45. napistu/network/net_propagation.py +47 -19
  46. napistu/network/{net_utils.py → ng_utils.py} +124 -272
  47. napistu/network/paths.py +67 -51
  48. napistu/network/precompute.py +11 -11
  49. napistu/ontologies/__init__.py +10 -0
  50. napistu/ontologies/constants.py +129 -0
  51. napistu/ontologies/dogma.py +243 -0
  52. napistu/ontologies/genodexito.py +649 -0
  53. napistu/ontologies/mygene.py +369 -0
  54. napistu/ontologies/renaming.py +198 -0
  55. napistu/rpy2/__init__.py +229 -86
  56. napistu/rpy2/callr.py +47 -77
  57. napistu/rpy2/constants.py +24 -23
  58. napistu/rpy2/rids.py +61 -648
  59. napistu/sbml_dfs_core.py +587 -222
  60. napistu/scverse/__init__.py +15 -0
  61. napistu/scverse/constants.py +28 -0
  62. napistu/scverse/loading.py +727 -0
  63. napistu/utils.py +118 -10
  64. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dev1.dist-info}/METADATA +8 -3
  65. napistu-0.3.1.dev1.dist-info/RECORD +133 -0
  66. tests/conftest.py +22 -0
  67. tests/test_context_discretize.py +56 -0
  68. tests/test_context_filtering.py +267 -0
  69. tests/test_identifiers.py +100 -0
  70. tests/test_indices.py +65 -0
  71. tests/{test_edgelist.py → test_ingestion_napistu_edgelist.py} +2 -2
  72. tests/test_matching_interactions.py +108 -0
  73. tests/test_matching_mount.py +305 -0
  74. tests/test_matching_species.py +394 -0
  75. tests/test_mcp_config.py +193 -0
  76. tests/test_mcp_documentation_utils.py +12 -3
  77. tests/test_mcp_server.py +156 -19
  78. tests/test_network_data_handling.py +397 -0
  79. tests/test_network_ig_utils.py +23 -0
  80. tests/test_network_neighborhoods.py +19 -0
  81. tests/test_network_net_create.py +459 -0
  82. tests/test_network_ng_utils.py +30 -0
  83. tests/test_network_paths.py +56 -0
  84. tests/{test_precomputed_distances.py → test_network_precompute.py} +8 -6
  85. tests/test_ontologies_genodexito.py +58 -0
  86. tests/test_ontologies_mygene.py +39 -0
  87. tests/test_ontologies_renaming.py +110 -0
  88. tests/test_rpy2_callr.py +79 -0
  89. tests/test_rpy2_init.py +151 -0
  90. tests/test_sbml.py +0 -31
  91. tests/test_sbml_dfs_core.py +134 -10
  92. tests/test_scverse_loading.py +778 -0
  93. tests/test_set_coverage.py +2 -2
  94. tests/test_utils.py +121 -1
  95. napistu/mechanism_matching.py +0 -1353
  96. napistu/rpy2/netcontextr.py +0 -467
  97. napistu-0.2.5.dev7.dist-info/RECORD +0 -98
  98. tests/test_igraph.py +0 -367
  99. tests/test_mechanism_matching.py +0 -784
  100. tests/test_net_utils.py +0 -149
  101. tests/test_netcontextr.py +0 -105
  102. tests/test_rpy2.py +0 -61
  103. /napistu/ingestion/{cpr_edgelist.py → napistu_edgelist.py} +0 -0
  104. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dev1.dist-info}/WHEEL +0 -0
  105. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dev1.dist-info}/entry_points.txt +0 -0
  106. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dev1.dist-info}/licenses/LICENSE +0 -0
  107. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dev1.dist-info}/top_level.txt +0 -0
  108. /tests/{test_obo.py → test_ingestion_obo.py} +0 -0
@@ -0,0 +1,193 @@
1
+ """
2
+ Tests for MCP configuration validation, presets, and Pydantic models.
3
+ """
4
+
5
+ import pytest
6
+ from pydantic import ValidationError
7
+ import click
8
+
9
+ from napistu.mcp.config import (
10
+ MCPServerConfig,
11
+ MCPClientConfig,
12
+ local_server_config,
13
+ production_server_config,
14
+ local_client_config,
15
+ production_client_config,
16
+ validate_server_config_flags,
17
+ validate_client_config_flags,
18
+ )
19
+ from napistu.mcp.constants import MCP_DEFAULTS
20
+
21
+
22
+ def test_configuration_validation():
23
+ """Test validation logic for preset conflicts and manual configuration requirements."""
24
+
25
+ # Test conflicting presets
26
+ with pytest.raises(
27
+ click.BadParameter, match="Cannot use both --local and --production"
28
+ ):
29
+ validate_server_config_flags(
30
+ local=True, production=True, host=None, port=None, server_name=None
31
+ )
32
+
33
+ with pytest.raises(
34
+ click.BadParameter, match="Cannot use both --local and --production"
35
+ ):
36
+ validate_client_config_flags(
37
+ local=True, production=True, host=None, port=None, use_https=None
38
+ )
39
+
40
+ # Test preset + manual config conflicts
41
+ with pytest.raises(click.BadParameter, match="Cannot use --local with manual"):
42
+ validate_server_config_flags(
43
+ local=True,
44
+ production=False,
45
+ host="192.168.1.1",
46
+ port=None,
47
+ server_name=None,
48
+ )
49
+
50
+ with pytest.raises(click.BadParameter, match="Cannot use --production with manual"):
51
+ validate_client_config_flags(
52
+ local=False, production=True, host="localhost", port=None, use_https=None
53
+ )
54
+
55
+ # Test incomplete manual configuration
56
+ with pytest.raises(
57
+ click.BadParameter,
58
+ match="Manual configuration requires --host, --port, and --server-name",
59
+ ):
60
+ validate_server_config_flags(
61
+ local=False, production=False, host="localhost", port=None, server_name=None
62
+ )
63
+
64
+ with pytest.raises(
65
+ click.BadParameter,
66
+ match="Manual configuration requires --host, --port, and --https",
67
+ ):
68
+ validate_client_config_flags(
69
+ local=False, production=False, host="localhost", port=8080, use_https=None
70
+ )
71
+
72
+ # Test valid configurations don't raise errors
73
+ config = validate_server_config_flags(
74
+ local=True, production=False, host=None, port=None, server_name=None
75
+ )
76
+ assert isinstance(config, MCPServerConfig)
77
+
78
+ config = validate_client_config_flags(
79
+ local=False, production=True, host=None, port=None, use_https=None
80
+ )
81
+ assert isinstance(config, MCPClientConfig)
82
+
83
+ # Test valid manual configuration
84
+ config = validate_server_config_flags(
85
+ local=False,
86
+ production=False,
87
+ host="localhost",
88
+ port=9000,
89
+ server_name="test-server",
90
+ )
91
+ assert config.host == "localhost"
92
+ assert config.port == 9000
93
+ assert config.server_name == "test-server"
94
+
95
+
96
+ def test_preset_configurations():
97
+ """Test that preset functions return correct and consistent configurations."""
98
+
99
+ # Test local server preset
100
+ local_server = local_server_config()
101
+ assert local_server.host == MCP_DEFAULTS.LOCAL_HOST
102
+ assert local_server.port == MCP_DEFAULTS.LOCAL_PORT
103
+ assert local_server.server_name == MCP_DEFAULTS.LOCAL_SERVER_NAME
104
+ assert (
105
+ local_server.bind_address
106
+ == f"{MCP_DEFAULTS.LOCAL_HOST}:{MCP_DEFAULTS.LOCAL_PORT}"
107
+ )
108
+
109
+ # Test production server preset
110
+ production_server = production_server_config()
111
+ assert production_server.host == MCP_DEFAULTS.PRODUCTION_HOST
112
+ assert production_server.port == MCP_DEFAULTS.PRODUCTION_PORT
113
+ assert production_server.server_name == MCP_DEFAULTS.PRODUCTION_SERVER_NAME
114
+
115
+ # Test local client preset
116
+ local_client = local_client_config()
117
+ assert local_client.host == MCP_DEFAULTS.LOCAL_HOST
118
+ assert local_client.port == MCP_DEFAULTS.LOCAL_PORT
119
+ assert not local_client.use_https
120
+ assert (
121
+ local_client.base_url
122
+ == f"http://{MCP_DEFAULTS.LOCAL_HOST}:{MCP_DEFAULTS.LOCAL_PORT}"
123
+ )
124
+ assert (
125
+ local_client.mcp_url
126
+ == f"http://{MCP_DEFAULTS.LOCAL_HOST}:{MCP_DEFAULTS.LOCAL_PORT}{MCP_DEFAULTS.MCP_PATH}"
127
+ )
128
+
129
+ # Test production client preset
130
+ production_client = production_client_config()
131
+ assert production_client.use_https
132
+ assert production_client.port == MCP_DEFAULTS.HTTPS_PORT
133
+ assert (
134
+ "napistu-mcp-server" in production_client.host
135
+ ) # Extracted from production URL
136
+ assert production_client.base_url.startswith("https://")
137
+ assert production_client.mcp_url.endswith(MCP_DEFAULTS.MCP_PATH)
138
+
139
+ # Test that presets are deterministic (same result on multiple calls)
140
+ assert local_server_config().host == local_server_config().host
141
+ assert production_client_config().port == production_client_config().port
142
+
143
+
144
+ def test_pydantic_model_validation():
145
+ """Test Pydantic model validation for ports, hosts, and computed properties."""
146
+
147
+ # Test valid configurations
148
+ valid_server = MCPServerConfig(host="localhost", port=8080, server_name="test")
149
+ assert valid_server.host == "localhost"
150
+ assert valid_server.port == 8080
151
+ assert valid_server.server_name == "test"
152
+ assert valid_server.bind_address == "localhost:8080"
153
+
154
+ valid_client = MCPClientConfig(host="example.com", port=443, use_https=True)
155
+ assert valid_client.host == "example.com"
156
+ assert valid_client.base_url == "https://example.com:443"
157
+ assert valid_client.mcp_url == f"https://example.com:443{MCP_DEFAULTS.MCP_PATH}"
158
+
159
+ # Test invalid port validation
160
+ with pytest.raises(
161
+ ValidationError, match="Input should be greater than or equal to 1"
162
+ ):
163
+ MCPServerConfig(host="localhost", port=0, server_name="test")
164
+
165
+ with pytest.raises(
166
+ ValidationError, match="Input should be less than or equal to 65535"
167
+ ):
168
+ MCPServerConfig(host="localhost", port=70000, server_name="test")
169
+
170
+ with pytest.raises(
171
+ ValidationError, match="Input should be greater than or equal to 1"
172
+ ):
173
+ MCPClientConfig(host="localhost", port=-1, use_https=False)
174
+
175
+ # Test empty/whitespace host validation
176
+ with pytest.raises(ValidationError, match="Host cannot be empty"):
177
+ MCPServerConfig(host="", port=8080, server_name="test")
178
+
179
+ with pytest.raises(ValidationError, match="Host cannot be empty"):
180
+ MCPServerConfig(host=" ", port=8080, server_name="test")
181
+
182
+ # Test host whitespace trimming
183
+ server_with_spaces = MCPServerConfig(
184
+ host=" localhost ", port=8080, server_name="test"
185
+ )
186
+ assert server_with_spaces.host == "localhost" # Should be trimmed
187
+
188
+ # Test URL generation with different protocols
189
+ http_client = MCPClientConfig(host="localhost", port=8080, use_https=False)
190
+ assert http_client.base_url == "http://localhost:8080"
191
+
192
+ https_client = MCPClientConfig(host="secure.com", port=443, use_https=True)
193
+ assert https_client.base_url == "https://secure.com:443"
@@ -1,13 +1,22 @@
1
1
  import pytest
2
- from napistu.mcp.documentation_utils import load_readme_content
3
- from napistu.mcp.constants import READMES
2
+ from napistu.mcp.documentation_utils import load_readme_content, fetch_wiki_page
3
+ from napistu.mcp.constants import READMES, WIKI_PAGES
4
4
 
5
5
 
6
6
  @pytest.mark.asyncio
7
- @pytest.mark.parametrize("name,url", READMES.items())
7
+ @pytest.mark.parametrize("name,url", list(READMES.items())[:2])
8
8
  async def test_load_readme_content(name, url):
9
9
  content = await load_readme_content(url)
10
10
  assert isinstance(content, str)
11
11
  assert len(content) > 0
12
12
  # Optionally, check for a keyword in the content
13
13
  assert "napistu" in content.lower() or "Napistu" in content
14
+
15
+
16
+ @pytest.mark.asyncio
17
+ @pytest.mark.parametrize("page_name", list(WIKI_PAGES)[:2])
18
+ async def test_fetch_wiki_page(page_name):
19
+ content = await fetch_wiki_page(page_name)
20
+ assert isinstance(content, str)
21
+ assert len(content) > 0
22
+ assert "napistu" in content.lower() or "Napistu" in content
tests/test_mcp_server.py CHANGED
@@ -48,7 +48,8 @@ def collect_resources_and_tools(mcp: FastMCP) -> Tuple[List[str], List[str]]:
48
48
  def test_documentation_naming():
49
49
  """Test that documentation component uses valid names."""
50
50
  mcp = FastMCP("test")
51
- documentation.register_components(mcp)
51
+ documentation_component = documentation.get_component()
52
+ documentation_component.register(mcp)
52
53
 
53
54
  resource_paths, tool_names = collect_resources_and_tools(mcp)
54
55
 
@@ -64,7 +65,8 @@ def test_documentation_naming():
64
65
  def test_codebase_naming():
65
66
  """Test that codebase component uses valid names."""
66
67
  mcp = FastMCP("test")
67
- codebase.register_components(mcp)
68
+ codebase_component = codebase.get_component()
69
+ codebase_component.register(mcp)
68
70
 
69
71
  resource_paths, tool_names = collect_resources_and_tools(mcp)
70
72
 
@@ -80,7 +82,8 @@ def test_codebase_naming():
80
82
  def test_tutorials_naming():
81
83
  """Test that tutorials component uses valid names."""
82
84
  mcp = FastMCP("test")
83
- tutorials.register_components(mcp)
85
+ tutorials_component = tutorials.get_component()
86
+ tutorials_component.register(mcp)
84
87
 
85
88
  resource_paths, tool_names = collect_resources_and_tools(mcp)
86
89
 
@@ -96,7 +99,14 @@ def test_tutorials_naming():
96
99
  def test_execution_naming():
97
100
  """Test that execution component uses valid names."""
98
101
  mcp = FastMCP("test")
99
- execution.register_components(mcp)
102
+
103
+ # Create execution component with test session context
104
+ test_session_context = {}
105
+ test_object_registry = {}
106
+ execution_component = execution.create_component(
107
+ session_context=test_session_context, object_registry=test_object_registry
108
+ )
109
+ execution_component.register(mcp)
100
110
 
101
111
  resource_paths, tool_names = collect_resources_and_tools(mcp)
102
112
 
@@ -129,11 +139,25 @@ def test_all_components_naming():
129
139
  """Test that all components together use valid names without conflicts."""
130
140
  mcp = FastMCP("test")
131
141
 
132
- # Register all components
133
- documentation.register_components(mcp)
134
- codebase.register_components(mcp)
135
- tutorials.register_components(mcp)
136
- execution.register_components(mcp)
142
+ # Register all components using new class-based API
143
+ documentation_component = documentation.get_component()
144
+ documentation_component.register(mcp)
145
+
146
+ codebase_component = codebase.get_component()
147
+ codebase_component.register(mcp)
148
+
149
+ tutorials_component = tutorials.get_component()
150
+ tutorials_component.register(mcp)
151
+
152
+ # Create execution component with test context
153
+ test_session_context = {}
154
+ test_object_registry = {}
155
+ execution_component = execution.create_component(
156
+ session_context=test_session_context, object_registry=test_object_registry
157
+ )
158
+ execution_component.register(mcp)
159
+
160
+ # Health component still uses legacy registration
137
161
  health.register_components(mcp)
138
162
 
139
163
  resource_paths, tool_names = collect_resources_and_tools(mcp)
@@ -157,11 +181,25 @@ def test_expected_resources_exist():
157
181
  """Test that all expected resources are registered."""
158
182
  mcp = FastMCP("test")
159
183
 
160
- # Register all components
161
- documentation.register_components(mcp)
162
- codebase.register_components(mcp)
163
- tutorials.register_components(mcp)
164
- execution.register_components(mcp)
184
+ # Register all components using new class-based API
185
+ documentation_component = documentation.get_component()
186
+ documentation_component.register(mcp)
187
+
188
+ codebase_component = codebase.get_component()
189
+ codebase_component.register(mcp)
190
+
191
+ tutorials_component = tutorials.get_component()
192
+ tutorials_component.register(mcp)
193
+
194
+ # Create execution component with test context
195
+ test_session_context = {}
196
+ test_object_registry = {}
197
+ execution_component = execution.create_component(
198
+ session_context=test_session_context, object_registry=test_object_registry
199
+ )
200
+ execution_component.register(mcp)
201
+
202
+ # Health component still uses legacy registration
165
203
  health.register_components(mcp)
166
204
 
167
205
  resource_paths, _ = collect_resources_and_tools(mcp)
@@ -191,15 +229,35 @@ def test_expected_tools_exist():
191
229
  """Test that all expected tools are registered."""
192
230
  mcp = FastMCP("test")
193
231
 
194
- # Register all components
195
- documentation.register_components(mcp)
196
- codebase.register_components(mcp)
197
- tutorials.register_components(mcp)
198
- execution.register_components(mcp)
232
+ # Register all components using new class-based API
233
+ documentation_component = documentation.get_component()
234
+ documentation_component.register(mcp)
235
+
236
+ codebase_component = codebase.get_component()
237
+ codebase_component.register(mcp)
238
+
239
+ tutorials_component = tutorials.get_component()
240
+ tutorials_component.register(mcp)
241
+
242
+ # Create execution component with test context
243
+ test_session_context = {}
244
+ test_object_registry = {}
245
+ execution_component = execution.create_component(
246
+ session_context=test_session_context, object_registry=test_object_registry
247
+ )
248
+ execution_component.register(mcp)
249
+
250
+ # Health component still uses legacy registration
199
251
  health.register_components(mcp)
200
252
 
201
253
  _, tool_names = collect_resources_and_tools(mcp)
202
254
 
255
+ # Debug: Print all registered tools
256
+ print("\nRegistered tools:")
257
+ for name in sorted(tool_names):
258
+ print(f" {name}")
259
+ print()
260
+
203
261
  # List of expected tools
204
262
  expected_tools = {
205
263
  "search_documentation",
@@ -217,3 +275,82 @@ def test_expected_tools_exist():
217
275
  # Check that all expected tools exist
218
276
  for tool in expected_tools:
219
277
  assert tool in tool_names, f"Missing expected tool: {tool}"
278
+
279
+
280
+ def test_component_state_health():
281
+ """Test that component states can provide health information."""
282
+ # Test documentation component state
283
+ doc_component = documentation.get_component()
284
+ doc_state = doc_component.get_state()
285
+ health_status = doc_state.get_health_status()
286
+ assert "status" in health_status
287
+ assert health_status["status"] in [
288
+ "initializing",
289
+ "healthy",
290
+ "inactive",
291
+ "unavailable",
292
+ ]
293
+
294
+ # Test codebase component state
295
+ codebase_component = codebase.get_component()
296
+ codebase_state = codebase_component.get_state()
297
+ health_status = codebase_state.get_health_status()
298
+ assert "status" in health_status
299
+ assert health_status["status"] in [
300
+ "initializing",
301
+ "healthy",
302
+ "inactive",
303
+ "unavailable",
304
+ ]
305
+
306
+ # Test tutorials component state
307
+ tutorials_component = tutorials.get_component()
308
+ tutorials_state = tutorials_component.get_state()
309
+ health_status = tutorials_state.get_health_status()
310
+ assert "status" in health_status
311
+ assert health_status["status"] in [
312
+ "initializing",
313
+ "healthy",
314
+ "inactive",
315
+ "unavailable",
316
+ ]
317
+
318
+ # Test execution component state
319
+ test_session_context = {"test": "value"}
320
+ test_object_registry = {}
321
+ execution_component = execution.create_component(
322
+ session_context=test_session_context, object_registry=test_object_registry
323
+ )
324
+ execution_state = execution_component.get_state()
325
+ health_status = execution_state.get_health_status()
326
+ assert "status" in health_status
327
+ assert health_status["status"] in [
328
+ "initializing",
329
+ "healthy",
330
+ "inactive",
331
+ "unavailable",
332
+ ]
333
+
334
+
335
+ def test_execution_object_registration():
336
+ """Test that execution component can register and track objects."""
337
+ test_session_context = {}
338
+ test_object_registry = {}
339
+ execution_component = execution.create_component(
340
+ session_context=test_session_context, object_registry=test_object_registry
341
+ )
342
+
343
+ # Register a test object
344
+ test_object = {"test": "data"}
345
+ execution_component.register_object("test_obj", test_object)
346
+
347
+ # Check that object was registered
348
+ state = execution_component.get_state()
349
+ assert "test_obj" in state.session_objects
350
+ assert state.session_objects["test_obj"] == test_object
351
+
352
+ # Check health details include the object
353
+ health_details = state.get_health_details()
354
+ assert "registered_objects" in health_details
355
+ assert health_details["registered_objects"] == 1
356
+ assert "test_obj" in health_details["object_names"]