diff --git a/fluidize/adapters/local/graph.py b/fluidize/adapters/local/graph.py index 70ea95f..70a4169 100644 --- a/fluidize/adapters/local/graph.py +++ b/fluidize/adapters/local/graph.py @@ -5,7 +5,7 @@ wrapping the core GraphProcessor with adapter-specific functionality. """ -from typing import Optional +from typing import TYPE_CHECKING, Optional from fluidize.core.modules.graph.processor import GraphProcessor from fluidize.core.types.graph import GraphData, GraphEdge, GraphNode @@ -14,6 +14,9 @@ from fluidize.core.types.project import ProjectSummary from fluidize.core.utils.pathfinder.path_finder import PathFinder +if TYPE_CHECKING: + from fluidize.managers.graph import InsertNodeRequest + class GraphHandler: """ @@ -40,20 +43,18 @@ def get_graph(self, project: ProjectSummary) -> GraphData: processor = GraphProcessor(project) return processor.get_graph() - def insert_node(self, project: ProjectSummary, node: GraphNode, sim_global: bool = True) -> GraphNode: + def insert_node(self, request: "InsertNodeRequest") -> GraphNode: """ Insert a new node into the project graph. Args: - project: The project to add the node to - node: The node to insert - sim_global: Whether to use global simulations (placeholder for future) + request: InsertNodeRequest containing node, project, and sim_global Returns: The inserted node """ - processor = GraphProcessor(project) - return processor.insert_node(node, sim_global) + processor = GraphProcessor(request.project) + return processor.insert_node(request.node, request.sim_global) def insert_node_from_scratch( self, diff --git a/fluidize/managers/graph.py b/fluidize/managers/graph.py index 3842d94..964a092 100644 --- a/fluidize/managers/graph.py +++ b/fluidize/managers/graph.py @@ -4,6 +4,8 @@ from typing import TYPE_CHECKING, Any, Optional +from pydantic import BaseModel + from fluidize.core.types.graph import GraphData, GraphEdge, GraphNode if TYPE_CHECKING: @@ -13,6 +15,15 @@ from fluidize.core.types.project import ProjectSummary +# TODO: the ty +class InsertNodeRequest(BaseModel): + """Uniform request structure for inserting nodes in both local and API modes.""" + + node: GraphNode + project: ProjectSummary + sim_global: bool = True + + class GraphManager: """ Graph manager for a specific project. @@ -68,7 +79,9 @@ def add_node(self, node: GraphNode, sim_global: bool = True) -> "NodeManager": Returns: The added node """ - inserted_node = self.adapter.graph.insert_node(self.project, node, sim_global) + # Create uniform request structure for both local and API modes + request = InsertNodeRequest(node=node, project=self.project, sim_global=sim_global) + inserted_node = self.adapter.graph.insert_node(request) return self.get_node(inserted_node.id) def add_node_from_scratch( diff --git a/pyproject.toml b/pyproject.toml index f0844da..3998446 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ classifiers = [ dependencies = [ "asciitree>=0.3.3", "docker>=7.1.0", - "fluidize-sdk>=0.6.0", + "fluidize-sdk>=0.7.0", "jinja2>=3.1.6", "mlflow>=3.1.4", "networkx>=3.2.1", diff --git a/tests/unit/backends/local/test_graph.py b/tests/unit/backends/local/test_graph.py index a61bbab..df0952f 100644 --- a/tests/unit/backends/local/test_graph.py +++ b/tests/unit/backends/local/test_graph.py @@ -7,6 +7,7 @@ from fluidize.adapters.local.graph import GraphHandler from fluidize.core.types.graph import GraphData from fluidize.core.types.parameters import Parameter +from fluidize.managers.graph import InsertNodeRequest from tests.fixtures.sample_graphs import SampleGraphs from tests.fixtures.sample_projects import SampleProjects @@ -77,7 +78,8 @@ def test_insert_node_success(self, graph_handler, mock_processor, sample_project node = SampleGraphs.sample_nodes()[0] mock_processor.insert_node.return_value = node - result = graph_handler.insert_node(sample_project, node, True) + request = InsertNodeRequest(node=node, project=sample_project, sim_global=True) + result = graph_handler.insert_node(request) assert result == node mock_processor.insert_node.assert_called_once_with(node, True) @@ -87,7 +89,8 @@ def test_insert_node_with_sim_global_false(self, graph_handler, mock_processor, node = SampleGraphs.sample_nodes()[1] mock_processor.insert_node.return_value = node - result = graph_handler.insert_node(sample_project, node, False) + request = InsertNodeRequest(node=node, project=sample_project, sim_global=False) + result = graph_handler.insert_node(request) assert result == node mock_processor.insert_node.assert_called_once_with(node, False) @@ -97,7 +100,8 @@ def test_insert_node_default_sim_global(self, graph_handler, mock_processor, sam node = SampleGraphs.sample_nodes()[0] mock_processor.insert_node.return_value = node - result = graph_handler.insert_node(sample_project, node) + request = InsertNodeRequest(node=node, project=sample_project) # sim_global defaults to True + result = graph_handler.insert_node(request) assert result == node mock_processor.insert_node.assert_called_once_with(node, True) # Default is True @@ -160,7 +164,8 @@ def test_processor_error_propagation_insert_node(self, graph_handler, mock_proce mock_processor.insert_node.side_effect = ValueError("Invalid node data") with pytest.raises(ValueError, match="Invalid node data"): - graph_handler.insert_node(sample_project, node) + request = InsertNodeRequest(node=node, project=sample_project) + graph_handler.insert_node(request) def test_processor_error_propagation_delete_node(self, graph_handler, mock_processor, sample_project): """Test that processor errors are propagated for delete_node.""" @@ -196,7 +201,8 @@ def test_processor_creation_per_operation(self, sample_project): # Perform multiple operations handler.get_graph(sample_project) - handler.insert_node(sample_project, SampleGraphs.sample_nodes()[0]) + request = InsertNodeRequest(node=SampleGraphs.sample_nodes()[0], project=sample_project) + handler.insert_node(request) handler.delete_node(sample_project, "test-id") # Verify processor was created for each operation @@ -245,7 +251,8 @@ def test_all_crud_operations_flow(self, sample_project): # Perform full CRUD cycle graph_data = handler.get_graph(sample_project) - inserted_node = handler.insert_node(sample_project, node) + request = InsertNodeRequest(node=node, project=sample_project) + inserted_node = handler.insert_node(request) updated_node = handler.update_node_position(sample_project, node) handler.delete_node(sample_project, "test-node-id") upserted_edge = handler.upsert_edge(sample_project, edge) @@ -297,6 +304,10 @@ def test_individual_operations(self, sample_project, operation, method_name, arg handler_method = getattr(handler, operation) if operation == "ensure_graph_initialized": handler_method(sample_project) + elif operation == "insert_node": + # insert_node now uses InsertNodeRequest + request = InsertNodeRequest(node=args[0], project=sample_project, sim_global=args[1]) + handler_method(request) else: handler_method(sample_project, *args) diff --git a/tests/unit/managers/test_project_graph.py b/tests/unit/managers/test_project_graph.py index f411022..cf5658a 100644 --- a/tests/unit/managers/test_project_graph.py +++ b/tests/unit/managers/test_project_graph.py @@ -8,7 +8,7 @@ from fluidize.core.types.node import author, nodeMetadata_simulation, nodeProperties_simulation, tag from fluidize.core.types.parameters import Parameter from fluidize.core.types.runs import RunStatus -from fluidize.managers.graph import GraphManager +from fluidize.managers.graph import GraphManager, InsertNodeRequest from fluidize.managers.node import NodeManager from tests.fixtures.sample_graphs import SampleGraphs from tests.fixtures.sample_projects import SampleProjects @@ -97,11 +97,14 @@ def test_add_node_success(self, project_graph, mock_adapter): assert isinstance(result, NodeManager) assert result.node_id == node.id - mock_adapter.graph.insert_node.assert_called_once_with( - project_graph.project, - node, - True, # Default sim_global=True - ) + # Verify that insert_node was called with an InsertNodeRequest + mock_adapter.graph.insert_node.assert_called_once() + call_args = mock_adapter.graph.insert_node.call_args[0] + request = call_args[0] + assert isinstance(request, InsertNodeRequest) + assert request.node == node + assert request.project == project_graph.project + assert request.sim_global is True def test_add_node_with_sim_global_false(self, project_graph, mock_adapter): """Test node addition with sim_global=False.""" @@ -112,7 +115,14 @@ def test_add_node_with_sim_global_false(self, project_graph, mock_adapter): assert isinstance(result, NodeManager) assert result.node_id == node.id - mock_adapter.graph.insert_node.assert_called_once_with(project_graph.project, node, False) + # Verify that insert_node was called with an InsertNodeRequest with sim_global=False + mock_adapter.graph.insert_node.assert_called_once() + call_args = mock_adapter.graph.insert_node.call_args[0] + request = call_args[0] + assert isinstance(request, InsertNodeRequest) + assert request.node == node + assert request.project == project_graph.project + assert request.sim_global is False def test_add_node_from_scratch_success(self, project_graph, mock_adapter): """Test successful node creation from scratch.""" @@ -296,8 +306,13 @@ def test_project_scoping(self, mock_adapter): # Verify each call was made with correct project context calls = mock_adapter.graph.insert_node.call_args_list assert len(calls) == 2 - assert calls[0][0][0] == project1 # First call with project1 - assert calls[1][0][0] == project2 # Second call with project2 + # Check that both calls received InsertNodeRequest with correct projects + request1 = calls[0][0][0] + request2 = calls[1][0][0] + assert isinstance(request1, InsertNodeRequest) + assert isinstance(request2, InsertNodeRequest) + assert request1.project == project1 # First call with project1 + assert request2.project == project2 # Second call with project2 def test_all_methods_delegate_to_adapter(self, project_graph, mock_adapter): """Test that all GraphManager methods properly delegate to adapter.""" @@ -386,9 +401,17 @@ def test_project_context_consistency(self, project_graph, mock_adapter): ] # All calls should include the same project as first argument - for call_list in all_calls: + # (except insert_node which now uses InsertNodeRequest) + for i, call_list in enumerate(all_calls): if call_list: # If method was called - assert call_list[0][0][0] == project + if i == 1: # insert_node call index + # For insert_node, check the project in the InsertNodeRequest + request = call_list[0][0][0] + assert isinstance(request, InsertNodeRequest) + assert request.project == project + else: + # For other calls, project is still the first argument + assert call_list[0][0][0] == project def test_get_parameters_success(self, project_graph, mock_adapter): """Test successful parameter retrieval through ProjectGraph.""" diff --git a/uv.lock b/uv.lock index e1f8dcf..f135c6e 100644 --- a/uv.lock +++ b/uv.lock @@ -759,7 +759,7 @@ dev = [ requires-dist = [ { name = "asciitree", specifier = ">=0.3.3" }, { name = "docker", specifier = ">=7.1.0" }, - { name = "fluidize-sdk", specifier = ">=0.6.0" }, + { name = "fluidize-sdk", specifier = ">=0.7.0" }, { name = "jinja2", specifier = ">=3.1.6" }, { name = "mlflow", specifier = ">=3.1.4" }, { name = "networkx", specifier = ">=3.2.1" }, @@ -786,7 +786,7 @@ dev = [ [[package]] name = "fluidize-sdk" -version = "0.6.0" +version = "0.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -796,9 +796,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f0/6d/41a511df1e9f2cfb6526a349162ea7032ef2ef3d4ced52b8ff81a036ca84/fluidize_sdk-0.6.0.tar.gz", hash = "sha256:cb4548e3ebac5c949b177a6f352edc91be5ffd82900dd0267159027ffb895633", size = 119144, upload-time = "2025-08-16T09:45:34.48Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/58/af4a33d85bdebfa45cf8994eff983ac23e98a1086cc8dd3cd4b1c4adad7e/fluidize_sdk-0.7.0.tar.gz", hash = "sha256:9f1e2b5c38409790ce36e0e5d85792575a2045c1780ba12a74f1fc7dec87ddb6", size = 119652, upload-time = "2025-08-18T00:45:01.689Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/6e/25a81bd993b6dec0733e7f4f13f199a40e3500222907bfab54fc5e946eea/fluidize_sdk-0.6.0-py3-none-any.whl", hash = "sha256:22db9101b88bbf0adea113057f96ce9436985ce3b9e635fedcdb421fff399ab2", size = 121524, upload-time = "2025-08-16T09:45:32.874Z" }, + { url = "https://files.pythonhosted.org/packages/01/e3/10c44e47f5fe50edb7d91fcdcc3613ec7ed8b34471e888d260f2415f52ae/fluidize_sdk-0.7.0-py3-none-any.whl", hash = "sha256:32744079456e5a5b6cc99e2ce1c48c9f42339f1e72a778383643711e0dcd1faf", size = 122274, upload-time = "2025-08-18T00:45:00.047Z" }, ] [[package]]