napistu 0.3.7__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,538 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+ import pandas as pd
6
+
7
+ from napistu.network import net_create_utils
8
+ from napistu.constants import (
9
+ MINI_SBO_FROM_NAME,
10
+ SBML_DFS,
11
+ SBOTERM_NAMES,
12
+ VALID_SBO_TERM_NAMES,
13
+ )
14
+ from napistu.network.constants import (
15
+ DROP_REACTIONS_WHEN,
16
+ NAPISTU_GRAPH_EDGES,
17
+ NAPISTU_GRAPH_NODE_TYPES,
18
+ VALID_GRAPH_WIRING_APPROACHES,
19
+ )
20
+
21
+
22
+ def test_format_interactors(reaction_species_examples):
23
+
24
+ r_id = "foo"
25
+ # interactions are formatted
26
+ graph_hierarchy_df = net_create_utils.create_graph_hierarchy_df("regulatory")
27
+
28
+ assert (
29
+ net_create_utils.format_tiered_reaction_species(
30
+ reaction_species_examples["valid_interactor"],
31
+ r_id,
32
+ graph_hierarchy_df,
33
+ drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
34
+ ).shape[0]
35
+ == 1
36
+ )
37
+
38
+ # simple reaction with just substrates and products
39
+ assert (
40
+ net_create_utils.format_tiered_reaction_species(
41
+ reaction_species_examples["sub_and_prod"],
42
+ r_id,
43
+ graph_hierarchy_df,
44
+ drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
45
+ ).shape[0]
46
+ == 2
47
+ )
48
+
49
+ # add a stimulator (activator)
50
+ rxn_edges = net_create_utils.format_tiered_reaction_species(
51
+ reaction_species_examples["stimulator"],
52
+ r_id,
53
+ graph_hierarchy_df,
54
+ drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
55
+ )
56
+
57
+ assert rxn_edges.shape[0] == 3
58
+ assert rxn_edges.iloc[0][["from", "to"]].tolist() == ["stim", "sub"]
59
+
60
+ # add catalyst + stimulator
61
+ rxn_edges = net_create_utils.format_tiered_reaction_species(
62
+ reaction_species_examples["all_entities"],
63
+ r_id,
64
+ graph_hierarchy_df,
65
+ drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
66
+ )
67
+
68
+ assert rxn_edges.shape[0] == 4
69
+ assert rxn_edges.iloc[0][["from", "to"]].tolist() == ["stim", "cat"]
70
+ assert rxn_edges.iloc[1][["from", "to"]].tolist() == ["cat", "sub"]
71
+
72
+ # no substrate
73
+ rxn_edges = net_create_utils.format_tiered_reaction_species(
74
+ reaction_species_examples["no_substrate"],
75
+ r_id,
76
+ graph_hierarchy_df,
77
+ drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
78
+ )
79
+
80
+ assert rxn_edges.shape[0] == 5
81
+ # stimulator -> reactant
82
+ assert rxn_edges.iloc[0][["from", "to"]].tolist() == ["stim1", "cat"]
83
+ assert rxn_edges.iloc[1][["from", "to"]].tolist() == ["stim2", "cat"]
84
+ assert rxn_edges.iloc[2][["from", "to"]].tolist() == ["inh", "cat"]
85
+
86
+ # use the surrogate model tiered layout also
87
+
88
+ graph_hierarchy_df = net_create_utils.create_graph_hierarchy_df("surrogate")
89
+
90
+ rxn_edges = net_create_utils.format_tiered_reaction_species(
91
+ reaction_species_examples["all_entities"],
92
+ r_id,
93
+ graph_hierarchy_df,
94
+ drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
95
+ )
96
+
97
+ assert rxn_edges.shape[0] == 4
98
+ assert rxn_edges.iloc[0][["from", "to"]].tolist() == ["stim", "sub"]
99
+ assert rxn_edges.iloc[1][["from", "to"]].tolist() == ["sub", "cat"]
100
+
101
+
102
+ def test_drop_reactions_when_parameters(reaction_species_examples):
103
+ """Test different drop_reactions_when parameter values and edge cases."""
104
+
105
+ r_id = "foo"
106
+ graph_hierarchy = net_create_utils.create_graph_hierarchy_df("regulatory")
107
+
108
+ # Test ALWAYS - should drop reaction regardless of tiers
109
+ edges_always = net_create_utils.format_tiered_reaction_species(
110
+ reaction_species_examples["all_entities"],
111
+ r_id,
112
+ graph_hierarchy,
113
+ DROP_REACTIONS_WHEN.ALWAYS,
114
+ )
115
+ assert r_id not in edges_always[NAPISTU_GRAPH_EDGES.FROM].values
116
+ assert r_id not in edges_always[NAPISTU_GRAPH_EDGES.TO].values
117
+
118
+ # Test EDGELIST with 2 species (should drop reaction)
119
+ edges_edgelist = net_create_utils.format_tiered_reaction_species(
120
+ reaction_species_examples["sub_and_prod"],
121
+ r_id,
122
+ graph_hierarchy,
123
+ DROP_REACTIONS_WHEN.EDGELIST,
124
+ )
125
+ assert r_id not in edges_edgelist[NAPISTU_GRAPH_EDGES.FROM].values
126
+
127
+ # Test EDGELIST with >2 species (should keep reaction)
128
+ edges_multi = net_create_utils.format_tiered_reaction_species(
129
+ reaction_species_examples["all_entities"],
130
+ r_id,
131
+ graph_hierarchy,
132
+ DROP_REACTIONS_WHEN.EDGELIST,
133
+ )
134
+ reaction_in_edges = (
135
+ r_id in edges_multi[NAPISTU_GRAPH_EDGES.FROM].values
136
+ or r_id in edges_multi[NAPISTU_GRAPH_EDGES.TO].values
137
+ )
138
+ assert reaction_in_edges
139
+
140
+ # Test invalid parameter
141
+ with pytest.raises(ValueError, match="Invalid drop_reactions"):
142
+ net_create_utils.format_tiered_reaction_species(
143
+ reaction_species_examples["sub_and_prod"],
144
+ r_id,
145
+ graph_hierarchy,
146
+ "INVALID_OPTION",
147
+ )
148
+
149
+
150
+ def test_edge_cases_and_validation(reaction_species_examples):
151
+ """Test edge cases, empty inputs, and validation errors."""
152
+
153
+ r_id = "foo"
154
+ graph_hierarchy = net_create_utils.create_graph_hierarchy_df("regulatory")
155
+
156
+ # Test single species
157
+ edges_single = net_create_utils.format_tiered_reaction_species(
158
+ reaction_species_examples["single_species"], r_id, graph_hierarchy
159
+ )
160
+ assert edges_single.empty
161
+
162
+ # Test validation with incorrectly indexed DataFrame (should raise error)
163
+ bad_df = reaction_species_examples[
164
+ "sub_and_prod"
165
+ ].reset_index() # Remove proper index
166
+ with pytest.raises(ValueError):
167
+ net_create_utils.format_tiered_reaction_species(bad_df, r_id, graph_hierarchy)
168
+
169
+ # Test activator and inhibitor only (should return empty DataFrame)
170
+ edges_ai = net_create_utils.format_tiered_reaction_species(
171
+ reaction_species_examples["activator_and_inhibitor_only"], r_id, graph_hierarchy
172
+ )
173
+ assert edges_ai.empty
174
+
175
+
176
+ def test_edgelist_should_have_one_edge():
177
+ """EDGELIST with 2 species should create exactly 1 edge, not 2"""
178
+
179
+ r_id = "foo"
180
+ reaction_df = pd.DataFrame(
181
+ {
182
+ SBML_DFS.SBO_TERM: [
183
+ MINI_SBO_FROM_NAME[SBOTERM_NAMES.REACTANT],
184
+ MINI_SBO_FROM_NAME[SBOTERM_NAMES.PRODUCT],
185
+ ],
186
+ SBML_DFS.SC_ID: ["sub", "prod"],
187
+ SBML_DFS.STOICHIOMETRY: [-1.0, 1.0],
188
+ }
189
+ ).set_index(SBML_DFS.SBO_TERM)
190
+
191
+ graph_hierarchy = net_create_utils.create_graph_hierarchy_df("regulatory")
192
+ edges = net_create_utils.format_tiered_reaction_species(
193
+ reaction_df, r_id, graph_hierarchy, DROP_REACTIONS_WHEN.EDGELIST
194
+ )
195
+
196
+ # Should be 1 edge, actually gets 2
197
+ assert len(edges) == 1, f"EDGELIST should create 1 edge, got {len(edges)}"
198
+
199
+
200
+ def test_edgelist_should_not_have_reaction_as_source():
201
+ """EDGELIST should not have reaction ID in FROM column"""
202
+
203
+ r_id = "foo"
204
+ reaction_df = pd.DataFrame(
205
+ {
206
+ SBML_DFS.SBO_TERM: [
207
+ MINI_SBO_FROM_NAME[SBOTERM_NAMES.REACTANT],
208
+ MINI_SBO_FROM_NAME[SBOTERM_NAMES.PRODUCT],
209
+ ],
210
+ SBML_DFS.SC_ID: ["sub", "prod"],
211
+ SBML_DFS.STOICHIOMETRY: [-1.0, 1.0],
212
+ }
213
+ ).set_index(SBML_DFS.SBO_TERM)
214
+
215
+ graph_hierarchy = net_create_utils.create_graph_hierarchy_df("regulatory")
216
+ edges = net_create_utils.format_tiered_reaction_species(
217
+ reaction_df, r_id, graph_hierarchy, DROP_REACTIONS_WHEN.EDGELIST
218
+ )
219
+
220
+ # Should not have 'foo' in FROM column, but it does
221
+ assert (
222
+ r_id not in edges["from"].values
223
+ ), f"Reaction {r_id} should not appear in FROM column"
224
+
225
+
226
+ def test_should_drop_reaction(reaction_species_examples):
227
+
228
+ r_id = "foo"
229
+
230
+ graph_hierarchy_df = net_create_utils.create_graph_hierarchy_df("regulatory")
231
+
232
+ rxn_species = reaction_species_examples["sub_and_prod"]
233
+ net_create_utils._validate_sbo_indexed_rsc_stoi(rxn_species)
234
+
235
+ # map reaction species to the tiers of the graph hierarchy. higher levels point to lower levels
236
+ # same-level entries point at each other only if there is only a single tier
237
+ entities_ordered_by_tier = net_create_utils._reaction_species_to_tiers(
238
+ rxn_species, graph_hierarchy_df, r_id
239
+ )
240
+
241
+ # this is an edgeliist (just 2 entries)
242
+ assert net_create_utils._should_drop_reaction(
243
+ entities_ordered_by_tier, drop_reactions_when=DROP_REACTIONS_WHEN.EDGELIST
244
+ )
245
+
246
+ # not the same tier
247
+ assert not net_create_utils._should_drop_reaction(
248
+ entities_ordered_by_tier, drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER
249
+ )
250
+
251
+
252
+ def test_graph_hierarchy_layouts():
253
+ REQUIRED_NAMES = VALID_SBO_TERM_NAMES + [NAPISTU_GRAPH_NODE_TYPES.REACTION]
254
+ for value in VALID_GRAPH_WIRING_APPROACHES:
255
+ layout_df = net_create_utils.create_graph_hierarchy_df(value)
256
+ # all terms should be represented
257
+ missing = set(REQUIRED_NAMES).difference(
258
+ set(layout_df[NAPISTU_GRAPH_EDGES.SBO_NAME])
259
+ )
260
+ assert not missing, f"Missing SBO names in {value}: {missing}"
261
+ # all terms should be unique
262
+ duplicated = layout_df[layout_df[NAPISTU_GRAPH_EDGES.SBO_NAME].duplicated()]
263
+ assert (
264
+ duplicated.empty
265
+ ), f"Duplicated SBO names in {value}: {duplicated[NAPISTU_GRAPH_EDGES.SBO_NAME].tolist()}"
266
+ # check that reaction is present and its by itself
267
+ reaction_tiers = layout_df[
268
+ layout_df[NAPISTU_GRAPH_EDGES.SBO_NAME] == NAPISTU_GRAPH_NODE_TYPES.REACTION
269
+ ]["tier"].unique()
270
+ assert (
271
+ len(reaction_tiers) == 1
272
+ ), f"'reaction' appears in multiple tiers in {value}: {reaction_tiers}"
273
+ reaction_tier = reaction_tiers[0]
274
+ reaction_tier_df = layout_df[layout_df["tier"] == reaction_tier]
275
+ assert (
276
+ reaction_tier_df.shape[0] == 1
277
+ and reaction_tier_df[NAPISTU_GRAPH_EDGES.SBO_NAME].iloc[0]
278
+ == NAPISTU_GRAPH_NODE_TYPES.REACTION
279
+ ), f"Tier {reaction_tier} in {value} should contain only 'reaction', but contains: {reaction_tier_df[NAPISTU_GRAPH_EDGES.SBO_NAME].tolist()}"
280
+
281
+
282
+ def test_identifying_and_formatting_interactor_duos(reaction_species_examples):
283
+
284
+ # directly specify interactions as a speed up to same tier procedure
285
+ interaction_template = reaction_species_examples["valid_interactor"]
286
+ interactor_species = pd.concat(
287
+ [
288
+ interaction_template.reset_index().assign(
289
+ r_id=str(i),
290
+ sc_id=lambda df: df[SBML_DFS.SC_ID].apply(lambda x: f"r{i}_{x}"),
291
+ )
292
+ for i in range(0, 10)
293
+ ]
294
+ )
295
+
296
+ invalid_interactor_template = reaction_species_examples["invalid_interactor"]
297
+ invalid_interactor_species = pd.concat(
298
+ [
299
+ invalid_interactor_template.reset_index().assign(
300
+ r_id=str(i),
301
+ sc_id=lambda df: df[SBML_DFS.SC_ID].apply(lambda x: f"r{i}_{x}"),
302
+ )
303
+ for i in range(10, 12)
304
+ ]
305
+ )
306
+
307
+ reaction_species = pd.concat([interactor_species, invalid_interactor_species])
308
+
309
+ matching_r_ids = net_create_utils._find_sbo_duos(
310
+ reaction_species, MINI_SBO_FROM_NAME[SBOTERM_NAMES.INTERACTOR]
311
+ )
312
+ assert set(matching_r_ids) == {str(i) for i in range(0, 10)}
313
+
314
+ interactor_duos = reaction_species.loc[
315
+ reaction_species[SBML_DFS.R_ID].isin(matching_r_ids)
316
+ ]
317
+ assert net_create_utils._interactor_duos_to_wide(interactor_duos).shape[0] == 10
318
+
319
+
320
+ def test_wire_reaction_species_mixed_interactions(reaction_species_examples):
321
+ """
322
+ Test wire_reaction_species function with a mix of interactor and non-interactor reactions.
323
+
324
+ This test verifies that the function correctly processes:
325
+ 1. Interactor pairs (processed en-masse)
326
+ 2. Non-interactor reactions (processed with tiered algorithms)
327
+ 3. Different wiring approaches
328
+ 4. Different drop_reactions_when conditions
329
+ """
330
+
331
+ # Create a mixed dataset with both interactor and non-interactor reactions
332
+ # Interactor reactions (should be processed en-masse)
333
+ interactor_template = reaction_species_examples["valid_interactor"]
334
+ interactor_species = pd.concat(
335
+ [
336
+ interactor_template.reset_index().assign(
337
+ r_id=f"interactor_{i}",
338
+ sc_id=lambda df: df[SBML_DFS.SC_ID].apply(
339
+ lambda x: f"interactor_{i}_{x}"
340
+ ),
341
+ )
342
+ for i in range(3) # 3 interactor reactions
343
+ ]
344
+ )
345
+
346
+ # Non-interactor reactions (should be processed with tiered algorithms)
347
+ non_interactor_species = pd.concat(
348
+ [
349
+ reaction_species_examples["sub_and_prod"]
350
+ .reset_index()
351
+ .assign(
352
+ r_id=f"reaction_{i}",
353
+ sc_id=lambda df: df[SBML_DFS.SC_ID].apply(
354
+ lambda x: f"reaction_{i}_{x}"
355
+ ),
356
+ )
357
+ for i in range(2) # 2 substrate-product reactions
358
+ ]
359
+ )
360
+
361
+ # Add a complex reaction with multiple entity types
362
+ complex_reaction = (
363
+ reaction_species_examples["all_entities"]
364
+ .reset_index()
365
+ .assign(
366
+ r_id="complex_reaction",
367
+ sc_id=lambda df: df[SBML_DFS.SC_ID].apply(lambda x: f"complex_{x}"),
368
+ )
369
+ )
370
+
371
+ # Combine all reaction species
372
+ all_reaction_species = pd.concat(
373
+ [interactor_species, non_interactor_species, complex_reaction]
374
+ )
375
+
376
+ # Test with regulatory wiring approach
377
+ edges_regulatory = net_create_utils.wire_reaction_species(
378
+ all_reaction_species,
379
+ wiring_approach="regulatory",
380
+ drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
381
+ )
382
+
383
+ # Verify the output structure
384
+ assert not edges_regulatory.empty
385
+ required_columns = [
386
+ NAPISTU_GRAPH_EDGES.FROM,
387
+ NAPISTU_GRAPH_EDGES.TO,
388
+ NAPISTU_GRAPH_EDGES.STOICHIOMETRY,
389
+ NAPISTU_GRAPH_EDGES.SBO_TERM,
390
+ SBML_DFS.R_ID,
391
+ ]
392
+
393
+ for col in required_columns:
394
+ assert col in edges_regulatory.columns, f"Missing required column: {col}"
395
+
396
+ # Check that interactor reactions were processed correctly
397
+ interactor_edges = edges_regulatory[
398
+ edges_regulatory[SBML_DFS.R_ID].str.startswith("interactor_")
399
+ ]
400
+ assert (
401
+ len(interactor_edges) == 3
402
+ ), f"Expected 3 interactor edges, got {len(interactor_edges)}"
403
+
404
+ # Check that non-interactor reactions were processed correctly
405
+ reaction_edges = edges_regulatory[
406
+ edges_regulatory[SBML_DFS.R_ID].str.startswith("reaction_")
407
+ ]
408
+ assert (
409
+ len(reaction_edges) == 4
410
+ ), f"Expected 4 reaction edges, got {len(reaction_edges)}"
411
+
412
+ # Check that complex reaction was processed correctly
413
+ complex_edges = edges_regulatory[
414
+ edges_regulatory[SBML_DFS.R_ID] == "complex_reaction"
415
+ ]
416
+ assert (
417
+ len(complex_edges) == 4
418
+ ), f"Expected 4 complex reaction edges, got {len(complex_edges)}"
419
+
420
+ # Test with different drop_reactions_when condition
421
+ edges_always_drop = net_create_utils.wire_reaction_species(
422
+ all_reaction_species,
423
+ wiring_approach="regulatory",
424
+ drop_reactions_when=DROP_REACTIONS_WHEN.ALWAYS,
425
+ )
426
+
427
+ # With ALWAYS drop, reaction IDs should not appear in FROM or TO columns
428
+ reaction_ids = [
429
+ "interactor_0",
430
+ "interactor_1",
431
+ "interactor_2",
432
+ "reaction_0",
433
+ "reaction_1",
434
+ "complex_reaction",
435
+ ]
436
+ for r_id in reaction_ids:
437
+ assert (
438
+ r_id not in edges_always_drop[NAPISTU_GRAPH_EDGES.FROM].values
439
+ ), f"Reaction {r_id} should not be in FROM column"
440
+ assert (
441
+ r_id not in edges_always_drop[NAPISTU_GRAPH_EDGES.TO].values
442
+ ), f"Reaction {r_id} should not be in TO column"
443
+
444
+ # Test with EDGELIST drop condition
445
+ edges_edgelist = net_create_utils.wire_reaction_species(
446
+ all_reaction_species,
447
+ wiring_approach="regulatory",
448
+ drop_reactions_when=DROP_REACTIONS_WHEN.EDGELIST,
449
+ )
450
+
451
+ # Simple reactions (2 species) should not have reaction IDs, but complex reactions should
452
+ simple_reaction_ids = ["reaction_0", "reaction_1"]
453
+ complex_reaction_id = "complex_reaction"
454
+
455
+ for r_id in simple_reaction_ids:
456
+ assert (
457
+ r_id not in edges_edgelist[NAPISTU_GRAPH_EDGES.FROM].values
458
+ ), f"Simple reaction {r_id} should not be in FROM column"
459
+ assert (
460
+ r_id not in edges_edgelist[NAPISTU_GRAPH_EDGES.TO].values
461
+ ), f"Simple reaction {r_id} should not be in TO column"
462
+
463
+ # Complex reaction should still have reaction ID in edges
464
+ complex_in_edges = (
465
+ complex_reaction_id in edges_edgelist[NAPISTU_GRAPH_EDGES.FROM].values
466
+ or complex_reaction_id in edges_edgelist[NAPISTU_GRAPH_EDGES.TO].values
467
+ )
468
+ assert (
469
+ complex_in_edges
470
+ ), f"Complex reaction {complex_reaction_id} should appear in edges"
471
+
472
+ # Test edge case: only interactor reactions
473
+ only_interactors = interactor_species
474
+ edges_only_interactors = net_create_utils.wire_reaction_species(
475
+ only_interactors,
476
+ wiring_approach="regulatory",
477
+ drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
478
+ )
479
+
480
+ assert not edges_only_interactors.empty
481
+ assert (
482
+ len(edges_only_interactors) == 3
483
+ ), f"Expected 3 edges for only interactors, got {len(edges_only_interactors)}"
484
+
485
+ # Test edge case: only non-interactor reactions
486
+ only_reactions = pd.concat([non_interactor_species, complex_reaction])
487
+ edges_only_reactions = net_create_utils.wire_reaction_species(
488
+ only_reactions,
489
+ wiring_approach="regulatory",
490
+ drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
491
+ )
492
+
493
+ print(edges_only_reactions)
494
+
495
+ assert not edges_only_reactions.empty
496
+ assert (
497
+ len(edges_only_reactions) == 8
498
+ ), f"Expected 8 edges for only reactions, got {len(edges_only_reactions)}"
499
+
500
+
501
+ def test_wire_reaction_species_validation_errors():
502
+ """Test wire_reaction_species function with invalid inputs."""
503
+
504
+ # Test with invalid wiring approach
505
+ reaction_species = pd.DataFrame(
506
+ {
507
+ SBML_DFS.R_ID: ["R1"],
508
+ SBML_DFS.SC_ID: ["A"],
509
+ SBML_DFS.STOICHIOMETRY: [0],
510
+ SBML_DFS.SBO_TERM: ["SBO:0000336"], # interactor
511
+ }
512
+ )
513
+
514
+ with pytest.raises(ValueError, match="is not a valid wiring approach"):
515
+ net_create_utils.wire_reaction_species(
516
+ reaction_species,
517
+ wiring_approach="invalid_approach",
518
+ drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
519
+ )
520
+
521
+ # Test with invalid SBO terms
522
+ invalid_sbo_species = pd.DataFrame(
523
+ {
524
+ SBML_DFS.R_ID: ["R1"],
525
+ SBML_DFS.SC_ID: ["A"],
526
+ SBML_DFS.STOICHIOMETRY: [0],
527
+ SBML_DFS.SBO_TERM: ["INVALID_SBO_TERM"],
528
+ }
529
+ )
530
+
531
+ with pytest.raises(
532
+ ValueError, match="Some reaction species have unusable SBO terms"
533
+ ):
534
+ net_create_utils.wire_reaction_species(
535
+ invalid_sbo_species,
536
+ wiring_approach="regulatory",
537
+ drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
538
+ )
@@ -0,0 +1,19 @@
1
+ import igraph as ig
2
+ import logging
3
+
4
+ from napistu.network.ng_core import NapistuGraph
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ def test_remove_isolated_vertices():
10
+ """Test removing isolated vertices from a graph."""
11
+
12
+ g = ig.Graph()
13
+ g.add_vertices(5, attributes={"name": ["A", "B", "C", "D", "E"]})
14
+ g.add_edges([(0, 1), (2, 3)]) # A-B, C-D connected; E isolated
15
+
16
+ napstu_graph = NapistuGraph.from_igraph(g)
17
+ napstu_graph.remove_isolated_vertices()
18
+ assert napstu_graph.vcount() == 4 # Should have 4 vertices after removing E
19
+ assert "E" not in [v["name"] for v in napstu_graph.vs] # E should be gone
@@ -2,7 +2,7 @@ import igraph as ig
2
2
  import pandas as pd
3
3
 
4
4
  from napistu.network import ng_utils
5
- from napistu.network.napistu_graph_core import NapistuGraph
5
+ from napistu.network.ng_core import NapistuGraph
6
6
 
7
7
 
8
8
  def test_napistu_graph_to_pandas_dfs():
@@ -7,6 +7,7 @@ import tempfile
7
7
  import numpy as np
8
8
  import pandas as pd
9
9
  from napistu import sbml_dfs_core
10
+ from napistu import utils
10
11
  from napistu.ingestion import sbml
11
12
  from napistu.network import neighborhoods
12
13
  from napistu.network import net_create
@@ -23,7 +24,7 @@ sbml_dfs = sbml_dfs_core.SBML_dfs(sbml_model)
23
24
  sbml_dfs.validate()
24
25
 
25
26
  napistu_graph = net_create.process_napistu_graph(
26
- sbml_dfs, graph_type="bipartite", directed=True, weighting_strategy="topology"
27
+ sbml_dfs, wiring_approach="bipartite", directed=True, weighting_strategy="topology"
27
28
  )
28
29
 
29
30
  # number of species to include when finding all x all paths
@@ -263,10 +264,10 @@ def test_precomputed_distances_serialization():
263
264
 
264
265
  try:
265
266
  # Test serialization
266
- precompute.save_precomputed_distances(original_df, temp_path)
267
+ utils.save_parquet(original_df, temp_path)
267
268
 
268
269
  # Test deserialization
269
- loaded_df = precompute.load_precomputed_distances(temp_path)
270
+ loaded_df = utils.load_parquet(temp_path)
270
271
 
271
272
  # Validate that the loaded DataFrame is identical to the original
272
273
  pd.testing.assert_frame_equal(original_df, loaded_df, check_like=True)
tests/test_rpy2_callr.py CHANGED
@@ -16,7 +16,6 @@ sys.modules["rpy2.robjects.pandas2ri"] = Mock()
16
16
  sys.modules["rpy2.rinterface"] = Mock()
17
17
  sys.modules["rpy2_arrow"] = Mock()
18
18
  sys.modules["rpy2_arrow.arrow"] = Mock()
19
- sys.modules["pyarrow"] = Mock()
20
19
 
21
20
  import napistu.rpy2.callr # noqa: E402
22
21
 
tests/test_rpy2_init.py CHANGED
@@ -15,7 +15,6 @@ sys.modules["rpy2.robjects.pandas2ri"] = Mock()
15
15
  sys.modules["rpy2.rinterface"] = Mock()
16
16
  sys.modules["rpy2_arrow"] = Mock()
17
17
  sys.modules["rpy2_arrow.arrow"] = Mock()
18
- sys.modules["pyarrow"] = Mock()
19
18
 
20
19
  import napistu.rpy2 # noqa: E402
21
20
 
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import pandas as pd
4
+ import pytest
4
5
 
5
6
  from napistu import sbml_dfs_utils
6
7
  from napistu.constants import (
@@ -10,6 +11,10 @@ from napistu.constants import (
10
11
  SBML_DFS,
11
12
  IDENTIFIERS,
12
13
  SBOTERM_NAMES,
14
+ VALID_SBO_TERMS,
15
+ VALID_SBO_TERM_NAMES,
16
+ MINI_SBO_FROM_NAME,
17
+ MINI_SBO_TO_NAME,
13
18
  )
14
19
 
15
20
 
@@ -219,3 +224,43 @@ def test_stubbed_compartment():
219
224
  "url": "https://www.ebi.ac.uk/QuickGO/term/GO:0005575",
220
225
  "bqb": "BQB_IS",
221
226
  }
227
+
228
+
229
+ def test_validate_sbo_values_success():
230
+ # Should not raise
231
+ sbml_dfs_utils._validate_sbo_values(pd.Series(VALID_SBO_TERMS), validate="terms")
232
+ sbml_dfs_utils._validate_sbo_values(
233
+ pd.Series(VALID_SBO_TERM_NAMES), validate="names"
234
+ )
235
+
236
+
237
+ def test_validate_sbo_values_invalid_type():
238
+ with pytest.raises(ValueError, match="Invalid validation type"):
239
+ sbml_dfs_utils._validate_sbo_values(
240
+ pd.Series(VALID_SBO_TERMS), validate="badtype"
241
+ )
242
+
243
+
244
+ def test_validate_sbo_values_invalid_value():
245
+ # Add an invalid term
246
+ s = pd.Series(VALID_SBO_TERMS + ["SBO:9999999"])
247
+ with pytest.raises(ValueError, match="unusable SBO terms"):
248
+ sbml_dfs_utils._validate_sbo_values(s, validate="terms")
249
+ # Add an invalid name
250
+ s = pd.Series(VALID_SBO_TERM_NAMES + ["not_a_name"])
251
+ with pytest.raises(ValueError, match="unusable SBO terms"):
252
+ sbml_dfs_utils._validate_sbo_values(s, validate="names")
253
+
254
+
255
+ def test_sbo_constants_internal_consistency():
256
+ # Every term should have a name and vice versa
257
+ # MINI_SBO_FROM_NAME: name -> term, MINI_SBO_TO_NAME: term -> name
258
+ terms_from_names = set(MINI_SBO_FROM_NAME.values())
259
+ names_from_terms = set(MINI_SBO_TO_NAME.values())
260
+ assert terms_from_names == set(VALID_SBO_TERMS)
261
+ assert names_from_terms == set(VALID_SBO_TERM_NAMES)
262
+ # Bijective mapping
263
+ for name, term in MINI_SBO_FROM_NAME.items():
264
+ assert MINI_SBO_TO_NAME[term] == name
265
+ for term, name in MINI_SBO_TO_NAME.items():
266
+ assert MINI_SBO_FROM_NAME[name] == term