Skip to content

Commit b0a9004

Browse files
authored
Merge pull request #19 from Fluidize-Inc/dev
Load metadata for run
2 parents 02a3f78 + 487ef90 commit b0a9004

6 files changed

Lines changed: 92 additions & 12 deletions

File tree

fluidize/adapters/local/runs.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from fluidize.core.modules.graph.process import ProcessGraph
1313
from fluidize.core.modules.run.project.project_runner import ProjectRunner
1414
from fluidize.core.types.project import ProjectSummary
15-
from fluidize.core.types.runs import RunFlowPayload
15+
from fluidize.core.types.runs import RunFlowPayload, projectRunMetadata
1616
from fluidize.core.utils.dataloader.data_loader import DataLoader
1717
from fluidize.core.utils.pathfinder.path_finder import PathFinder
1818

@@ -110,7 +110,7 @@ def list_runs(self, project: ProjectSummary) -> list[str]:
110110
"""
111111
return DataLoader.list_runs(project)
112112

113-
def get_run_status(self, project: ProjectSummary, run_number: int) -> dict[str, Any]:
113+
def get_run_metadata(self, project: ProjectSummary, run_number: int) -> projectRunMetadata:
114114
"""
115115
Get the status of a specific run.
116116
@@ -121,9 +121,7 @@ def get_run_status(self, project: ProjectSummary, run_number: int) -> dict[str,
121121
Returns:
122122
Dictionary with run status information
123123
"""
124-
# This would load run metadata and return status
125-
# Implementation depends on how run status is stored
126-
return {"run_number": run_number, "status": "unknown"}
124+
return projectRunMetadata.from_file(directory=PathFinder.get_run_path(project, run_number))
127125

128126
def list_node_outputs(self, project: ProjectSummary, run_number: int, node_id: str) -> list[str]:
129127
"""

fluidize/core/modules/graph/processor.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def get_graph(self) -> GraphData:
6060
print(f"Error loading graph for project {self.project.id}: {e!s}")
6161
return GraphData(nodes=[], edges=[])
6262

63+
# TODO : FIX THIS GRAPH NODE ADDITION HERE IN THE API! (THE TRAILING SLASHES GIVE PROBLEMS WHEN COPYING NODE DIRECTORY)
6364
def insert_node(self, node: GraphNode, sim_global: bool = True) -> GraphNode:
6465
"""
6566
Inserts a node from the list of simulations or creates a new one.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""
2+
Custom exceptions for the Fluidize project.
3+
4+
This module provides custom exception classes for better error handling
5+
and debugging throughout the Fluidize application.
6+
"""
7+
8+
9+
class FluidizeError(Exception):
10+
"""Base exception class for all Fluidize-related errors."""
11+
12+
pass
13+
14+
15+
class ProjectAlreadyExistsError(FluidizeError):
16+
"""Raised when attempting to create a project that already exists."""
17+
18+
def __init__(self, project_id: str) -> None:
19+
"""
20+
Initialize the exception.
21+
22+
Args:
23+
project_id: The ID of the project that already exists
24+
"""
25+
super().__init__(f"Project '{project_id}' already exists. Use update to modify existing projects.")
26+
self.project_id = project_id

fluidize/managers/registry.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from typing import Any, Optional
22

3+
from fluidize.core.utils.exceptions import ProjectAlreadyExistsError
4+
35
from .project import ProjectManager
46

57

@@ -38,7 +40,19 @@ def create(
3840
3941
Returns:
4042
Created project wrapped in Project class
43+
44+
Raises:
45+
ProjectAlreadyExistsError: If a project with the same ID already exists
4146
"""
47+
# Check if project already exists
48+
try:
49+
self.get(project_id)
50+
# If we get here, project exists - raise error
51+
raise ProjectAlreadyExistsError(project_id)
52+
except FileNotFoundError:
53+
# Project doesn't exist, proceed with creation
54+
pass
55+
4256
project_summary = self.adapter.projects.upsert(
4357
id=project_id,
4458
label=label,

fluidize/managers/runs.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from upath import UPath
88

99
from fluidize.core.types.project import ProjectSummary
10-
from fluidize.core.types.runs import RunFlowPayload
10+
from fluidize.core.types.runs import RunFlowPayload, projectRunMetadata
1111

1212

1313
class RunsManager:
@@ -48,17 +48,17 @@ def list_runs(self) -> list[str]:
4848
"""
4949
return self.adapter.runs.list_runs(self.project) # type: ignore[no-any-return]
5050

51-
def get_status(self, run_number: int) -> dict[str, Any]:
51+
def get_metadata(self, run_number: int) -> projectRunMetadata:
5252
"""
53-
Get the status of a specific run for this project.
53+
Get the metadata of a specific run for this project.
5454
5555
Args:
5656
run_number: The run number to check
5757
5858
Returns:
59-
Dictionary with run status information
59+
Dictionary with run metadata information
6060
"""
61-
return self.adapter.runs.get_run_status(self.project, run_number) # type: ignore[no-any-return]
61+
return self.adapter.runs.get_run_metadata(self.project, run_number) # type: ignore[no-any-return]
6262

6363
def list_node_outputs(self, run_number: int, node_id: str) -> list[str]:
6464
"""

tests/unit/managers/test_projects.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import pytest
66

7+
from fluidize.core.utils.exceptions import ProjectAlreadyExistsError
78
from fluidize.managers.registry import RegistryManager
89
from tests.fixtures.sample_projects import SampleProjects
910

@@ -35,6 +36,8 @@ def test_create_project_with_all_fields(self, projects_manager, mock_adapter):
3536

3637
sample_project = SampleProjects.standard_project()
3738
mock_adapter.projects.upsert.return_value = sample_project
39+
# Mock retrieve to raise FileNotFoundError (project doesn't exist yet)
40+
mock_adapter.projects.retrieve.side_effect = FileNotFoundError("Project not found")
3841

3942
result = projects_manager.create(
4043
project_id=sample_project.id,
@@ -65,6 +68,8 @@ def test_create_project_minimal(self, projects_manager, mock_adapter):
6568
project_id = "minimal-create"
6669
minimal_project = SampleProjects.minimal_project()
6770
mock_adapter.projects.upsert.return_value = minimal_project
71+
# Mock retrieve to raise FileNotFoundError (project doesn't exist yet)
72+
mock_adapter.projects.retrieve.side_effect = FileNotFoundError("Project not found")
6873

6974
result = projects_manager.create(project_id)
7075

@@ -81,6 +86,8 @@ def test_create_project_partial_fields(self, projects_manager, mock_adapter):
8186

8287
sample_project = SampleProjects.standard_project()
8388
mock_adapter.projects.upsert.return_value = sample_project
89+
# Mock retrieve to raise FileNotFoundError (project doesn't exist yet)
90+
mock_adapter.projects.retrieve.side_effect = FileNotFoundError("Project not found")
8491

8592
result = projects_manager.create(
8693
project_id="partial-create", label="Partial Project", description="Only some fields provided"
@@ -120,6 +127,25 @@ def test_get_project_not_found(self, projects_manager, mock_adapter):
120127

121128
mock_adapter.projects.retrieve.assert_called_once_with(project_id)
122129

130+
def test_create_project_already_exists(self, projects_manager, mock_adapter):
131+
"""Test create method raises error when project already exists."""
132+
sample_project = SampleProjects.standard_project()
133+
project_id = sample_project.id
134+
135+
# Mock get to return existing project (no FileNotFoundError)
136+
mock_adapter.projects.retrieve.return_value = sample_project
137+
138+
with pytest.raises(ProjectAlreadyExistsError) as exc_info:
139+
projects_manager.create(project_id, label="New Label")
140+
141+
# Verify error message contains project ID
142+
assert project_id in str(exc_info.value)
143+
assert exc_info.value.project_id == project_id
144+
145+
# Verify retrieve was called but upsert was not
146+
mock_adapter.projects.retrieve.assert_called_once_with(project_id)
147+
mock_adapter.projects.upsert.assert_not_called()
148+
123149
def test_list_projects_empty(self, projects_manager, mock_adapter):
124150
"""Test list method when no projects exist."""
125151
mock_adapter.projects.list.return_value = []
@@ -258,7 +284,8 @@ def test_update_filters_none_values(self, projects_manager, mock_adapter):
258284

259285
def test_adapter_error_propagation(self, projects_manager, mock_adapter):
260286
"""Test that adapter errors are properly propagated through manager methods."""
261-
# Test create error
287+
# Test create error - first mock retrieve to return FileNotFoundError (project doesn't exist)
288+
mock_adapter.projects.retrieve.side_effect = FileNotFoundError("Project not found")
262289
mock_adapter.projects.upsert.side_effect = ValueError("Invalid project data")
263290

264291
with pytest.raises(ValueError, match="Invalid project data"):
@@ -290,14 +317,20 @@ def test_manager_adapter_delegation(self, mock_adapter):
290317
mock_adapter.projects.list.return_value = [test_project]
291318

292319
# Call all manager methods
320+
# Mock retrieve to raise FileNotFoundError (project doesn't exist yet)
321+
mock_adapter.projects.retrieve.side_effect = [
322+
FileNotFoundError("Project not found"),
323+
test_project,
324+
test_project,
325+
]
293326
manager.create("test-create")
294327
manager.get("test-get")
295328
manager.list()
296329
manager.update("test-update", label="Updated")
297330

298331
# Verify adapter was called
299332
assert mock_adapter.projects.upsert.call_count == 2 # create + update
300-
mock_adapter.projects.retrieve.assert_called_once()
333+
assert mock_adapter.projects.retrieve.call_count == 2 # create (check if exists) + get
301334
mock_adapter.projects.list.assert_called_once()
302335

303336
def test_manager_interface_compatibility(self, mock_adapter):
@@ -327,6 +360,12 @@ def test_project_wrapper_return_types(self, mock_adapter):
327360
mock_adapter.projects.list.return_value = [sample_project]
328361

329362
# Test create returns Project wrapper
363+
# Mock retrieve to raise FileNotFoundError for create (project doesn't exist yet)
364+
mock_adapter.projects.retrieve.side_effect = [
365+
FileNotFoundError("Project not found"),
366+
sample_project,
367+
sample_project,
368+
]
330369
created_project = manager.create("test-create")
331370
assert isinstance(created_project, ProjectManager)
332371
assert created_project.id == sample_project.id
@@ -356,6 +395,8 @@ def test_project_wrapper_graph_property_access(self, mock_adapter):
356395
mock_adapter.projects.upsert.return_value = sample_project
357396
mock_adapter.graph = Mock() # Mock graph handler
358397

398+
# Mock retrieve to raise FileNotFoundError (project doesn't exist yet)
399+
mock_adapter.projects.retrieve.side_effect = FileNotFoundError("Project not found")
359400
project = manager.create("test-graph-access")
360401

361402
# Verify project has graph property

0 commit comments

Comments
 (0)