From 1ed591482a43976adc77372e30c5213fa4869626 Mon Sep 17 00:00:00 2001 From: Henry Bae <69275685+BaeHenryS@users.noreply.github.com> Date: Fri, 15 Aug 2025 15:38:35 -0700 Subject: [PATCH 1/3] Node Class Added for Easy manipulation of node properties, parameters, metadata --- .../MUJOCO/Mujoco-Simulation/parameters.json | 3 - fluidize/adapters/local/graph.py | 35 +- .../types/file_models/json_file_model_base.py | 106 +++++ .../types/file_models/parameters_model.py | 56 +++ fluidize/core/types/node.py | 13 + fluidize/managers/graph.py | 33 +- fluidize/managers/node.py | 446 ++++++++++++++++++ tests/unit/backends/local/test_graph.py | 195 ++++---- tests/unit/managers/test_node.py | 199 ++++++++ tests/unit/managers/test_project_graph.py | 16 +- 10 files changed, 981 insertions(+), 121 deletions(-) create mode 100644 fluidize/core/types/file_models/json_file_model_base.py create mode 100644 fluidize/core/types/file_models/parameters_model.py create mode 100644 fluidize/managers/node.py create mode 100644 tests/unit/managers/test_node.py diff --git a/examples/example-projects/MUJOCO/Mujoco-Simulation/parameters.json b/examples/example-projects/MUJOCO/Mujoco-Simulation/parameters.json index 7940dbc..57b41a6 100644 --- a/examples/example-projects/MUJOCO/Mujoco-Simulation/parameters.json +++ b/examples/example-projects/MUJOCO/Mujoco-Simulation/parameters.json @@ -8,9 +8,6 @@ "name": "motor_strength", "latex": null, "location": [ - "source/pinata_simulation.py", - "source/pinata_simulation.py", - "source/pinata_simulation.py", "source/pinata_simulation.py" ], "options": null, diff --git a/fluidize/adapters/local/graph.py b/fluidize/adapters/local/graph.py index 0f5aedc..70ea95f 100644 --- a/fluidize/adapters/local/graph.py +++ b/fluidize/adapters/local/graph.py @@ -7,14 +7,11 @@ from typing import Optional -from fluidize.core.modules.graph.parameters import parse_parameters_from_json from fluidize.core.modules.graph.processor import GraphProcessor from fluidize.core.types.graph import GraphData, GraphEdge, GraphNode -from fluidize.core.types.node import nodeMetadata_simulation, nodeProperties_simulation +from fluidize.core.types.node import nodeMetadata_simulation, nodeParameters_simulation, nodeProperties_simulation from fluidize.core.types.parameters import Parameter from fluidize.core.types.project import ProjectSummary -from fluidize.core.utils.dataloader.data_loader import DataLoader -from fluidize.core.utils.dataloader.data_writer import DataWriter from fluidize.core.utils.pathfinder.path_finder import PathFinder @@ -171,9 +168,9 @@ def get_parameters(self, project: ProjectSummary, node_id: str) -> list[Paramete Returns: A list of Parameter objects for the node """ - parameters_path = PathFinder.get_node_parameters_path(project, node_id) - data = DataLoader.load_json(parameters_path) - return parse_parameters_from_json(data) + node_path = PathFinder.get_node_path(project, node_id) + parameters_model = nodeParameters_simulation.from_file(node_path) + return parameters_model.parameters def upsert_parameter(self, project: ProjectSummary, node_id: str, parameter: Parameter) -> Parameter: """ @@ -187,12 +184,11 @@ def upsert_parameter(self, project: ProjectSummary, node_id: str, parameter: Par Returns: The upserted parameter """ - parameters_path = PathFinder.get_node_parameters_path(project, node_id) - data = DataLoader.load_json(parameters_path) - params = parse_parameters_from_json(data) + node_path = PathFinder.get_node_path(project, node_id) + parameters_model = nodeParameters_simulation.from_file(node_path) # Check if parameter with same name exists - for p in params: + for p in parameters_model.parameters: if p.name == parameter.name: # Update the existing parameter with new values p.value = parameter.value @@ -211,13 +207,10 @@ def upsert_parameter(self, project: ProjectSummary, node_id: str, parameter: Par break else: # Parameter doesn't exist, add it - params.append(parameter) + parameters_model.parameters.append(parameter) - # Write updated parameters back - DataWriter.write_json( - filepath=parameters_path, - data={"parameters": [p.model_dump() for p in params]}, - ) + # Save updated parameters back + parameters_model.save() return parameter def set_parameters(self, project: ProjectSummary, node_id: str, parameters: list[Parameter]) -> list[Parameter]: @@ -232,10 +225,10 @@ def set_parameters(self, project: ProjectSummary, node_id: str, parameters: list Returns: The list of parameters that were set """ - parameters_path = PathFinder.get_node_parameters_path(project, node_id) - data = {"parameters": [p.model_dump() for p in parameters]} - - DataWriter.write_json(filepath=parameters_path, data=data) + node_path = PathFinder.get_node_path(project, node_id) + parameters_model = nodeParameters_simulation.from_file(node_path) + parameters_model.parameters = parameters + parameters_model.save() return parameters def show_parameters(self, project: ProjectSummary, node_id: str) -> str: diff --git a/fluidize/core/types/file_models/json_file_model_base.py b/fluidize/core/types/file_models/json_file_model_base.py new file mode 100644 index 0000000..b0e8ff2 --- /dev/null +++ b/fluidize/core/types/file_models/json_file_model_base.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +from typing import Any, TypeVar, Union + +from pydantic import BaseModel, ConfigDict, PrivateAttr, ValidationError +from upath import UPath + +T = TypeVar("T", bound="JSONFileModelBase") + + +class JSONFileModelBase(BaseModel): + _filepath: Union[UPath, None] = PrivateAttr(default=None) + + @property + def filepath(self) -> UPath: + """Return the exact path to the model file. Raises if not set.""" + if not self._filepath: + raise ValueError() + return self._filepath + + @property + def directory(self) -> UPath: + """Return the folder containing the model file. Raises if filepath not set.""" + fp = self.filepath + return fp.parent + + @classmethod + def from_file(cls: type[T], directory: Union[str, UPath]) -> T: + from fluidize.core.utils.dataloader.data_loader import DataLoader + + filename = getattr(cls, "_filename", None) + if not filename: + raise TypeError() + + path = UPath(directory) / filename + data = DataLoader.load_json(path) + + if not data: + raise FileNotFoundError() + + try: + instance = cls.model_validate(data) + except ValidationError: + raise + except Exception as e: + raise ValueError() from e + else: + instance._filepath = path + return instance + + @classmethod + def from_dict_and_path(cls: type[T], data: dict, path: UPath) -> T: + """Creates a model instance from a dictionary and a path, without reading the file again.""" + if not data: + raise ValueError() + + try: + instance = cls.model_validate(data) + except ValidationError: + raise + except Exception as e: + raise ValueError() from e + else: + instance._filepath = path + return instance + + def model_dump_wrapped(self) -> dict[str, Any]: + config = getattr(self, "Key", None) + key = getattr(config, "key", None) + + if not key: + return self.model_dump() + + return {key: self.model_dump(mode="json")} + + def save(self, directory: UPath | None = None) -> None: + from fluidize.core.utils.dataloader.data_loader import DataLoader + from fluidize.core.utils.dataloader.data_writer import DataWriter + + if directory: + filename = getattr(self.__class__, "_filename", None) + if not filename: + raise TypeError() + self._filepath = UPath(directory) / filename + + if not self._filepath: + raise ValueError() + + # Load existing data to preserve other keys, if the file already exists. + # Pass a new UPath object to avoid issues with object caching if it's the same file. + existing_data = DataLoader.load_json(UPath(self._filepath)) + + new_data = self.model_dump_wrapped() + existing_data.update(new_data) + + DataWriter.write_json(self._filepath, existing_data) + + def edit(self, **kwargs: Any) -> None: + for key, value in kwargs.items(): + if hasattr(self, key): + setattr(self, key, value) + else: + raise AttributeError() + self.save() + + model_config = ConfigDict(arbitrary_types_allowed=True) diff --git a/fluidize/core/types/file_models/parameters_model.py b/fluidize/core/types/file_models/parameters_model.py new file mode 100644 index 0000000..2c2abd4 --- /dev/null +++ b/fluidize/core/types/file_models/parameters_model.py @@ -0,0 +1,56 @@ +from typing import Any, ClassVar + +from pydantic import Field, model_validator + +from fluidize.core.constants import FileConstants +from fluidize.core.types.parameters import Parameter + +from .json_file_model_base import JSONFileModelBase + + +class ParametersModel(JSONFileModelBase): + _filename: ClassVar[str] = FileConstants.PARAMETERS_SUFFIX + """ + A base model for parameters objects stored in JSON structure. + + This model provides two main functionalities: + 1. A validator to automatically unpack nested data based on a 'key' + from the subclass's Config. + 2. A method to wrap the model's data back into the nested structure + for serialization. + """ + + parameters: list[Parameter] = Field(default_factory=list) + + @model_validator(mode="before") + @classmethod + def _unpack_and_validate(cls, data: Any) -> Any: + """ + Unpacks and validates the data against the key + specified in the subclass's Config. + """ + if not isinstance(data, dict): + return data + + config = getattr(cls, "Key", None) + key = getattr(config, "key", None) + + # If there's no key in the config or the key is not in the data, + # assume the data is already in the correct, unpacked structure. + if not key or key not in data: + return data + + unpacked_data = data[key] + if not isinstance(unpacked_data, list): + # If parameters is not a list, treat it as empty + unpacked_data = [] + + # Return data in the format expected by the model + return {"parameters": unpacked_data} + + def model_dump_wrapped(self) -> dict[str, Any]: + """Override to avoid double wrapping of parameters key.""" + return {"parameters": [p.model_dump() for p in self.parameters]} + + class Key: + key = "parameters" diff --git a/fluidize/core/types/node.py b/fluidize/core/types/node.py index 9e49ce4..15a2d23 100644 --- a/fluidize/core/types/node.py +++ b/fluidize/core/types/node.py @@ -11,6 +11,7 @@ from pydantic import BaseModel, ConfigDict, computed_field from .file_models.metadata_model import MetadataModel +from .file_models.parameters_model import ParametersModel from .file_models.properties_model import PropertiesModel from .runs import RunStatus @@ -95,3 +96,15 @@ class nodeMetadata_simulation(MetadataModel): class Key: key = "simulation" metadata_version = "1.0" + + +class nodeParameters_simulation(ParametersModel): + """ + Parameters configuration for a simulation node. + + Handles loading and saving of parameters.json files with the structure: + {"parameters": [list of Parameter objects]} + """ + + class Key: + key = "parameters" diff --git a/fluidize/managers/graph.py b/fluidize/managers/graph.py index 1c4292d..6e7738f 100644 --- a/fluidize/managers/graph.py +++ b/fluidize/managers/graph.py @@ -2,9 +2,12 @@ Project-scoped graph manager for user-friendly graph operations. """ -from typing import Any, Optional +from typing import TYPE_CHECKING, Any, Optional from fluidize.core.types.graph import GraphData, GraphEdge, GraphNode + +if TYPE_CHECKING: + from .node import NodeManager from fluidize.core.types.node import nodeMetadata_simulation, nodeProperties_simulation from fluidize.core.types.parameters import Parameter from fluidize.core.types.project import ProjectSummary @@ -42,7 +45,21 @@ def get(self) -> GraphData: """ return self.adapter.graph.get_graph(self.project) # type: ignore[no-any-return] - def add_node(self, node: GraphNode, sim_global: bool = True) -> GraphNode: + def get_node(self, node_id: str) -> "NodeManager": + """ + Get a NodeManager for a specific node in the project. + + Args: + node_id: ID of the node to get a manager for + + Returns: + NodeManager instance for the specified node + """ + from .node import NodeManager + + return NodeManager(self.adapter, self.project, node_id) + + def add_node(self, node: GraphNode, sim_global: bool = True) -> "NodeManager": """ Add a new node to this project's graph. @@ -51,9 +68,10 @@ def add_node(self, node: GraphNode, sim_global: bool = True) -> GraphNode: sim_global: Whether to use global simulations (placeholder for future) Returns: - The inserted node + NodeManager for the inserted node """ - return self.adapter.graph.insert_node(self.project, node, sim_global) # type: ignore[no-any-return] + inserted_node = self.adapter.graph.insert_node(self.project, node, sim_global) + return self.get_node(inserted_node.id) def add_node_from_scratch( self, @@ -61,7 +79,7 @@ def add_node_from_scratch( node_properties: nodeProperties_simulation, node_metadata: nodeMetadata_simulation, repo_link: Optional[str] = None, - ) -> GraphNode: + ) -> "NodeManager": """ Add a new node to this project's graph from scratch, creating all necessary files and directories. @@ -72,11 +90,12 @@ def add_node_from_scratch( repo_link: Optional repository URL to clone into the source directory Returns: - The inserted node + NodeManager for the inserted node """ - return self.adapter.graph.insert_node_from_scratch( # type: ignore[no-any-return] + inserted_node = self.adapter.graph.insert_node_from_scratch( self.project, node, node_properties, node_metadata, repo_link ) + return self.get_node(inserted_node.id) def update_node_position(self, node: GraphNode) -> GraphNode: """ diff --git a/fluidize/managers/node.py b/fluidize/managers/node.py new file mode 100644 index 0000000..cef596e --- /dev/null +++ b/fluidize/managers/node.py @@ -0,0 +1,446 @@ +""" +Node-scoped manager for user-friendly node operations. +""" + +from typing import Any, Optional + +from upath import UPath + +from fluidize.core.constants import FileConstants +from fluidize.core.types.graph import GraphNode +from fluidize.core.types.node import nodeMetadata_simulation, nodeParameters_simulation, nodeProperties_simulation +from fluidize.core.types.parameters import Parameter +from fluidize.core.types.project import ProjectSummary +from fluidize.core.utils.pathfinder.path_finder import PathFinder + + +class NodeManager: + """ + Node manager for a specific node within a project. + + Provides node-specific operations like editing parameters, metadata, + and properties without requiring project and node context on each method call. + """ + + def __init__(self, adapter: Any, project: ProjectSummary, node_id: str) -> None: + """ + Initialize node-scoped manager. + + Args: + adapter: adapter adapter (FluidizeSDK or Localadapter) + project: The project this node belongs to + node_id: The ID of the node this manager is bound to + """ + self.adapter = adapter + self.project = project + self.node_id = node_id + + @property + def id(self) -> str: + """ + Get the node ID. + + Returns: + The ID of the node this manager is bound to + """ + return self.node_id + + @property + def data(self) -> Any: + """ + Get the node's data. + + Returns: + The data of the graph node + """ + return self.get_node().data + + def get_node(self) -> GraphNode: + """ + Get the complete graph node data. + + Returns: + GraphNode containing the node data + + Raises: + ValueError: If the node is not found in the project graph + """ + graph = self.adapter.graph.get_graph(self.project) + for node in graph.nodes: + if node.id == self.node_id: + return node # type: ignore[no-any-return] + msg = f"Node with ID '{self.node_id}' not found in project '{self.project.id}'" + raise ValueError(msg) + + def exists(self) -> bool: + """ + Check if this node exists in the project graph. + + Returns: + True if the node exists, False otherwise + """ + try: + self.get_node() + except ValueError: + return False + else: + return True + + def delete(self) -> None: + """ + Delete this node from the project graph and filesystem. + """ + self.adapter.graph.delete_node(self.project, self.node_id) + + def update_position(self, x: float, y: float) -> GraphNode: + """ + Update the node's position in the graph. + + Args: + x: New x coordinate + y: New y coordinate + + Returns: + The updated graph node + """ + node = self.get_node() + node.position.x = x + node.position.y = y + return self.adapter.graph.update_node_position(self.project, node) # type: ignore[no-any-return] + + def get_metadata(self) -> nodeMetadata_simulation: + """ + Get the node's metadata from metadata.yaml. + + Returns: + The node's metadata + + Raises: + FileNotFoundError: If metadata file doesn't exist + ValueError: If metadata file is invalid + """ + node_path = PathFinder.get_node_path(self.project, self.node_id) + return nodeMetadata_simulation.from_file(node_path) + + def update_metadata(self, **kwargs: Any) -> nodeMetadata_simulation: + """ + Update specific fields in the node's metadata. + + Args: + **kwargs: Fields to update (e.g., name="New Name", description="New desc") + + Returns: + The updated metadata + + Raises: + AttributeError: If trying to update a field that doesn't exist + """ + metadata = self.get_metadata() + metadata.edit(**kwargs) + return metadata + + def save_metadata(self, metadata: nodeMetadata_simulation) -> None: + """ + Save metadata object to the node's metadata.yaml file. + + Args: + metadata: The metadata object to save + """ + node_path = PathFinder.get_node_path(self.project, self.node_id) + metadata.save(node_path) + + def get_properties(self) -> nodeProperties_simulation: + """ + Get the node's properties from properties.yaml. + + Returns: + The node's properties + + Raises: + FileNotFoundError: If properties file doesn't exist + ValueError: If properties file is invalid + """ + node_path = PathFinder.get_node_path(self.project, self.node_id) + return nodeProperties_simulation.from_file(node_path) + + def update_properties(self, **kwargs: Any) -> nodeProperties_simulation: + """ + Update specific fields in the node's properties. + + Args: + **kwargs: Fields to update (e.g., container_image="new:tag", should_run=False) + + Returns: + The updated properties + + Raises: + AttributeError: If trying to update a field that doesn't exist + """ + properties = self.get_properties() + properties.edit(**kwargs) + return properties + + def save_properties(self, properties: nodeProperties_simulation) -> None: + """ + Save properties object to the node's properties.yaml file. + + Args: + properties: The properties object to save + """ + node_path = PathFinder.get_node_path(self.project, self.node_id) + properties.save(node_path) + + def get_parameters_model(self) -> nodeParameters_simulation: + """ + Get the node's parameters model from parameters.json. + + Returns: + The node's parameters model + + Raises: + FileNotFoundError: If parameters file doesn't exist + ValueError: If parameters file is invalid + """ + node_path = PathFinder.get_node_path(self.project, self.node_id) + return nodeParameters_simulation.from_file(node_path) + + def get_parameters(self) -> list[Parameter]: + """ + Get the node's parameters list from parameters.json. + + Returns: + List of Parameter objects for the node + """ + return self.get_parameters_model().parameters + + def get_parameter(self, name: str) -> Optional[Parameter]: + """ + Get a specific parameter by name. + + Args: + name: Name of the parameter to retrieve + + Returns: + The parameter if found, None otherwise + """ + parameters = self.get_parameters() + for param in parameters: + if param.name == name: + return param + return None + + def update_parameter(self, parameter: Parameter) -> Parameter: + """ + Update or add a parameter. + + Args: + parameter: The parameter to update/add + + Returns: + The updated parameter + """ + parameters_model = self.get_parameters_model() + + # Check if parameter with same name exists + for p in parameters_model.parameters: + if p.name == parameter.name: + # Update existing parameter + p.value = parameter.value + p.description = parameter.description + p.type = parameter.type + p.label = parameter.label + p.latex = parameter.latex + p.options = parameter.options + p.scope = parameter.scope + # Handle location extension + if parameter.location: + if p.location: + p.location.extend(parameter.location) + else: + p.location = parameter.location + break + else: + # Parameter doesn't exist, add it + parameters_model.parameters.append(parameter) + + parameters_model.save() + return parameter + + def set_parameters(self, parameters: list[Parameter]) -> list[Parameter]: + """ + Replace all parameters with the provided list. + + Args: + parameters: List of parameters to set + W + Returns: + The list of parameters that were set + """ + parameters_model = self.get_parameters_model() + parameters_model.parameters = parameters + parameters_model.save() + return parameters + + def remove_parameter(self, name: str) -> bool: + """ + Remove a parameter by name. + + Args: + name: Name of the parameter to remove + + Returns: + True if parameter was removed, False if it didn't exist + """ + parameters_model = self.get_parameters_model() + original_count = len(parameters_model.parameters) + parameters_model.parameters = [p for p in parameters_model.parameters if p.name != name] + + if len(parameters_model.parameters) < original_count: + parameters_model.save() + return True + return False + + def show_parameters(self) -> str: + """ + Get a formatted string display of all parameters. + + Returns: + A formatted string displaying the parameters + """ + parameters = self.get_parameters() + + if not parameters: + return f"No parameters found for node '{self.node_id}'" + + output = f"Parameters for node '{self.node_id}':\n\n" + + for i, param in enumerate(parameters, 1): + output += f"Parameter {i}:\n" + output += f" Name: {param.name}\n" + output += f" Value: {param.value}\n" + output += f" Description: {param.description}\n" + output += f" Type: {param.type}\n" + output += f" Label: {param.label}\n" + if param.latex: + output += f" LaTeX: {param.latex}\n" + if param.location: + output += f" Location: {param.location}\n" + if param.options: + output += f" Options: {[opt.label for opt in param.options]}\n" + if param.scope: + output += f" Scope: {param.scope}\n" + output += "\n" + + return output + + def get_node_directory(self) -> UPath: + """ + Get the filesystem path to this node's directory. + + Returns: + Path to the node's directory + """ + return PathFinder.get_node_path(self.project, self.node_id) + + def get_metadata_path(self) -> UPath: + """ + Get the filesystem path to this node's metadata.yaml file. + + Returns: + Path to the metadata file + """ + return self.get_node_directory() / FileConstants.METADATA_SUFFIX + + def get_properties_path(self) -> UPath: + """ + Get the filesystem path to this node's properties.yaml file. + + Returns: + Path to the properties file + """ + return PathFinder.get_properties_path(self.project, self.node_id) + + def get_parameters_path(self) -> UPath: + """ + Get the filesystem path to this node's parameters.json file. + + Returns: + Path to the parameters file + """ + return PathFinder.get_node_parameters_path(self.project, self.node_id) + + def validate(self) -> dict[str, Any]: + """ + Validate the node's files and structure. + + Returns: + Dictionary containing validation results with keys: + - 'valid': bool indicating if node is valid + - 'graph_node_exists': bool + - 'metadata_exists': bool + - 'properties_exists': bool + - 'parameters_exists': bool + - 'errors': list of error messages + """ + result: dict[str, Any] = { + "valid": True, + "graph_node_exists": False, + "metadata_exists": False, + "properties_exists": False, + "parameters_exists": False, + "errors": [], + } + + # Check if node exists in graph + try: + self.get_node() + result["graph_node_exists"] = True + except ValueError as e: + result["errors"].append(str(e)) + + # Check metadata file + try: + self.get_metadata() + result["metadata_exists"] = True + except Exception as e: + result["errors"].append(f"Metadata error: {e}") + + # Check properties file + try: + self.get_properties() + result["properties_exists"] = True + except Exception as e: + result["errors"].append(f"Properties error: {e}") + + # Check parameters file + try: + self.get_parameters() + result["parameters_exists"] = True + except Exception as e: + result["errors"].append(f"Parameters error: {e}") + + result["valid"] = len(result["errors"]) == 0 + return result + + def to_dict(self) -> dict[str, Any]: + """ + Convert the complete node information to a dictionary. + + Returns: + Dictionary containing node graph data, metadata, properties, and parameters + """ + try: + return { + "graph_node": self.get_node().model_dump(), + "metadata": self.get_metadata().model_dump(), + "properties": self.get_properties().model_dump(), + "parameters": [p.model_dump() for p in self.get_parameters()], + "paths": { + "node_directory": str(self.get_node_directory()), + "metadata_file": str(self.get_metadata_path()), + "properties_file": str(self.get_properties_path()), + "parameters_file": str(self.get_parameters_path()), + }, + } + except Exception as e: + return {"error": str(e), "node_id": self.node_id, "project": self.project.id} diff --git a/tests/unit/backends/local/test_graph.py b/tests/unit/backends/local/test_graph.py index dfb197b..a61bbab 100644 --- a/tests/unit/backends/local/test_graph.py +++ b/tests/unit/backends/local/test_graph.py @@ -310,31 +310,33 @@ def test_individual_operations(self, sample_project, operation, method_name, arg else: processor_method.assert_called_once() - @patch("fluidize.adapters.local.graph.DataLoader") + @patch("fluidize.adapters.local.graph.nodeParameters_simulation") @patch("fluidize.adapters.local.graph.PathFinder") - def test_get_parameters_success(self, mock_pathfinder, mock_dataloader, sample_project): + def test_get_parameters_success(self, mock_pathfinder, mock_node_params, sample_project): """Test successful parameter retrieval.""" # Mock setup mock_parameters_path = Mock() - mock_pathfinder.get_node_parameters_path.return_value = mock_parameters_path - mock_dataloader.load_json.return_value = { - "parameters": [ - { - "name": "test_param", - "value": "test_value", - "type": "text", - "label": "Test Parameter", - "description": "A test parameter", - } - ] - } + mock_pathfinder.get_node_path.return_value = mock_parameters_path + + # Mock the parameters model instance + mock_params_instance = Mock() + mock_params_instance.parameters = [ + Parameter( + name="test_param", + value="test_value", + type="text", + label="Test Parameter", + description="A test parameter", + ) + ] + mock_node_params.from_file.return_value = mock_params_instance handler = GraphHandler() result = handler.get_parameters(sample_project, "test-node-id") # Verify calls - mock_pathfinder.get_node_parameters_path.assert_called_once_with(sample_project, "test-node-id") - mock_dataloader.load_json.assert_called_once_with(mock_parameters_path) + mock_pathfinder.get_node_path.assert_called_once_with(sample_project, "test-node-id") + mock_node_params.from_file.assert_called_once_with(mock_parameters_path) # Verify result assert len(result) == 1 @@ -342,16 +344,19 @@ def test_get_parameters_success(self, mock_pathfinder, mock_dataloader, sample_p assert result[0].name == "test_param" assert result[0].value == "test_value" - @patch("fluidize.adapters.local.graph.DataWriter") - @patch("fluidize.adapters.local.graph.DataLoader") + @patch("fluidize.adapters.local.graph.nodeParameters_simulation") @patch("fluidize.adapters.local.graph.PathFinder") - def test_upsert_parameter_new_parameter(self, mock_pathfinder, mock_dataloader, mock_datawriter, sample_project): + def test_upsert_parameter_new_parameter(self, mock_pathfinder, mock_node_params, sample_project): """Test upserting a new parameter.""" # Mock setup mock_parameters_path = Mock() - mock_pathfinder.get_node_parameters_path.return_value = mock_parameters_path - mock_dataloader.load_json.return_value = {"parameters": []} - mock_datawriter.write_json.return_value = True + mock_pathfinder.get_node_path.return_value = mock_parameters_path + + # Mock the parameters model instance with empty parameters + mock_params_instance = Mock() + mock_params_instance.parameters = [] + mock_node_params.from_file.return_value = mock_params_instance + mock_params_instance.save.return_value = None new_parameter = Parameter( name="new_param", value="new_value", type="text", label="New Parameter", description="A new parameter" @@ -361,41 +366,38 @@ def test_upsert_parameter_new_parameter(self, mock_pathfinder, mock_dataloader, result = handler.upsert_parameter(sample_project, "test-node-id", new_parameter) # Verify calls - mock_pathfinder.get_node_parameters_path.assert_called_once_with(sample_project, "test-node-id") - mock_dataloader.load_json.assert_called_once_with(mock_parameters_path) - mock_datawriter.write_json.assert_called_once() + mock_pathfinder.get_node_path.assert_called_once_with(sample_project, "test-node-id") + mock_node_params.from_file.assert_called_once_with(mock_parameters_path) + mock_params_instance.save.assert_called_once() - # Verify the written data contains the new parameter - written_data = mock_datawriter.write_json.call_args[1]["data"] - assert len(written_data["parameters"]) == 1 - assert written_data["parameters"][0]["name"] == "new_param" + # Verify the parameter was added to the instance + assert len(mock_params_instance.parameters) == 1 + assert mock_params_instance.parameters[0].name == "new_param" # Verify result assert result == new_parameter - @patch("fluidize.adapters.local.graph.DataWriter") - @patch("fluidize.adapters.local.graph.DataLoader") + @patch("fluidize.adapters.local.graph.nodeParameters_simulation") @patch("fluidize.adapters.local.graph.PathFinder") - def test_upsert_parameter_existing_parameter( - self, mock_pathfinder, mock_dataloader, mock_datawriter, sample_project - ): + def test_upsert_parameter_existing_parameter(self, mock_pathfinder, mock_node_params, sample_project): """Test upserting an existing parameter extends locations.""" # Mock setup with existing parameter mock_parameters_path = Mock() - mock_pathfinder.get_node_parameters_path.return_value = mock_parameters_path - mock_dataloader.load_json.return_value = { - "parameters": [ - { - "name": "existing_param", - "value": "existing_value", - "type": "text", - "label": "Existing Parameter", - "description": "An existing parameter", - "location": ["file1.py"], - } - ] - } - mock_datawriter.write_json.return_value = True + mock_pathfinder.get_node_path.return_value = mock_parameters_path + + # Mock the parameters model instance with existing parameter + existing_param = Parameter( + name="existing_param", + value="existing_value", + type="text", + label="Existing Parameter", + description="An existing parameter", + location=["file1.py"], + ) + mock_params_instance = Mock() + mock_params_instance.parameters = [existing_param] + mock_node_params.from_file.return_value = mock_params_instance + mock_params_instance.save.return_value = None update_parameter = Parameter( name="existing_param", @@ -409,24 +411,32 @@ def test_upsert_parameter_existing_parameter( handler = GraphHandler() result = handler.upsert_parameter(sample_project, "test-node-id", update_parameter) - # Verify the written data extends the location - written_data = mock_datawriter.write_json.call_args[1]["data"] - assert len(written_data["parameters"]) == 1 - param_data = written_data["parameters"][0] - assert param_data["name"] == "existing_param" - assert param_data["location"] == ["file1.py", "file2.py"] + # Verify calls + mock_pathfinder.get_node_path.assert_called_once_with(sample_project, "test-node-id") + mock_node_params.from_file.assert_called_once_with(mock_parameters_path) + mock_params_instance.save.assert_called_once() + + # Verify the parameter location was extended + updated_param = mock_params_instance.parameters[0] + assert updated_param.name == "existing_param" + assert updated_param.location == ["file1.py", "file2.py"] # Verify result assert result == update_parameter - @patch("fluidize.adapters.local.graph.DataWriter") + @patch("fluidize.adapters.local.graph.nodeParameters_simulation") @patch("fluidize.adapters.local.graph.PathFinder") - def test_set_parameters_success(self, mock_pathfinder, mock_datawriter, sample_project): + def test_set_parameters_success(self, mock_pathfinder, mock_node_params, sample_project): """Test setting parameters replaces all existing parameters.""" # Mock setup mock_parameters_path = Mock() - mock_pathfinder.get_node_parameters_path.return_value = mock_parameters_path - mock_datawriter.write_json.return_value = True + mock_pathfinder.get_node_path.return_value = mock_parameters_path + + # Mock the parameters model instance + mock_params_instance = Mock() + mock_params_instance.parameters = [] + mock_node_params.from_file.return_value = mock_params_instance + mock_params_instance.save.return_value = None parameters = [ Parameter(name="param1", value="value1", type="text", label="Parameter 1", description="First parameter"), @@ -439,42 +449,49 @@ def test_set_parameters_success(self, mock_pathfinder, mock_datawriter, sample_p result = handler.set_parameters(sample_project, "test-node-id", parameters) # Verify calls - mock_pathfinder.get_node_parameters_path.assert_called_once_with(sample_project, "test-node-id") - mock_datawriter.write_json.assert_called_once() + mock_pathfinder.get_node_path.assert_called_once_with(sample_project, "test-node-id") + mock_node_params.from_file.assert_called_once_with(mock_parameters_path) + mock_params_instance.save.assert_called_once() - # Verify the written data - written_data = mock_datawriter.write_json.call_args[1]["data"] - assert len(written_data["parameters"]) == 2 - assert written_data["parameters"][0]["name"] == "param1" - assert written_data["parameters"][1]["name"] == "param2" + # Verify the parameters were set correctly + assert mock_params_instance.parameters == parameters + assert len(mock_params_instance.parameters) == 2 + assert mock_params_instance.parameters[0].name == "param1" + assert mock_params_instance.parameters[1].name == "param2" # Verify result assert result == parameters - @patch("fluidize.adapters.local.graph.DataLoader") + @patch("fluidize.adapters.local.graph.nodeParameters_simulation") @patch("fluidize.adapters.local.graph.PathFinder") - def test_show_parameters_success(self, mock_pathfinder, mock_dataloader, sample_project): + def test_show_parameters_success(self, mock_pathfinder, mock_node_params, sample_project): """Test showing parameters in nice format.""" # Mock setup mock_parameters_path = Mock() - mock_pathfinder.get_node_parameters_path.return_value = mock_parameters_path - mock_dataloader.load_json.return_value = { - "parameters": [ - { - "name": "motor_strength", - "value": "20.0", - "type": "text", - "label": "Motor Strength", - "description": "Control signal strength for bat motor", - "scope": "simulation", - "location": ["source/pinata_simulation.py"], - } - ] - } + mock_pathfinder.get_node_path.return_value = mock_parameters_path + + # Mock the parameters model instance with a parameter + mock_params_instance = Mock() + mock_params_instance.parameters = [ + Parameter( + name="motor_strength", + value="20.0", + type="text", + label="Motor Strength", + description="Control signal strength for bat motor", + scope="simulation", + location=["source/pinata_simulation.py"], + ) + ] + mock_node_params.from_file.return_value = mock_params_instance handler = GraphHandler() result = handler.show_parameters(sample_project, "test-node-id") + # Verify calls + mock_pathfinder.get_node_path.assert_called_once_with(sample_project, "test-node-id") + mock_node_params.from_file.assert_called_once_with(mock_parameters_path) + # Verify the formatted output contains expected content assert "Parameters for node 'test-node-id':" in result assert "Name: motor_strength" in result @@ -485,17 +502,25 @@ def test_show_parameters_success(self, mock_pathfinder, mock_dataloader, sample_ assert "Scope: simulation" in result assert "Location: source/pinata_simulation.py" in result - @patch("fluidize.adapters.local.graph.DataLoader") + @patch("fluidize.adapters.local.graph.nodeParameters_simulation") @patch("fluidize.adapters.local.graph.PathFinder") - def test_show_parameters_no_parameters(self, mock_pathfinder, mock_dataloader, sample_project): + def test_show_parameters_no_parameters(self, mock_pathfinder, mock_node_params, sample_project): """Test showing parameters when none exist.""" # Mock setup for empty parameters mock_parameters_path = Mock() - mock_pathfinder.get_node_parameters_path.return_value = mock_parameters_path - mock_dataloader.load_json.return_value = {"parameters": []} + mock_pathfinder.get_node_path.return_value = mock_parameters_path + + # Mock the parameters model instance with empty parameters + mock_params_instance = Mock() + mock_params_instance.parameters = [] + mock_node_params.from_file.return_value = mock_params_instance handler = GraphHandler() result = handler.show_parameters(sample_project, "empty-node-id") + # Verify calls + mock_pathfinder.get_node_path.assert_called_once_with(sample_project, "empty-node-id") + mock_node_params.from_file.assert_called_once_with(mock_parameters_path) + # Verify the no parameters message assert result == "No parameters found for node 'empty-node-id'" diff --git a/tests/unit/managers/test_node.py b/tests/unit/managers/test_node.py new file mode 100644 index 0000000..6b7d906 --- /dev/null +++ b/tests/unit/managers/test_node.py @@ -0,0 +1,199 @@ +"""Unit tests for NodeManager - node-scoped operations.""" + +from unittest.mock import Mock, patch + +import pytest + +from fluidize.core.types.graph import GraphData, GraphNode, Position, graphNodeData +from fluidize.core.types.parameters import Parameter +from fluidize.managers.node import NodeManager +from tests.fixtures.sample_projects import SampleProjects + + +class TestNodeManager: + """Test suite for NodeManager class.""" + + @pytest.fixture + def mock_adapter(self): + """Create a mock adapter with graph handler.""" + adapter = Mock() + adapter.graph = Mock() + return adapter + + @pytest.fixture + def sample_project(self): + """Sample project for testing.""" + return SampleProjects.standard_project() + + @pytest.fixture + def sample_node(self): + """Sample graph node for testing.""" + return GraphNode( + id="test-node-001", + position=Position(x=100.0, y=200.0), + data=graphNodeData(label="Test Node", simulation_id="test-sim-001"), + type="simulation", + ) + + @pytest.fixture + def node_manager(self, mock_adapter, sample_project): + """Create a NodeManager instance for testing.""" + return NodeManager(mock_adapter, sample_project, "test-node-001") + + def test_init(self, mock_adapter, sample_project): + """Test NodeManager initialization.""" + node_manager = NodeManager(mock_adapter, sample_project, "test-node-001") + + assert node_manager.adapter is mock_adapter + assert node_manager.project is sample_project + assert node_manager.node_id == "test-node-001" + + def test_get_node_success(self, node_manager, mock_adapter, sample_node): + """Test successful node retrieval.""" + graph_data = GraphData(nodes=[sample_node], edges=[]) + mock_adapter.graph.get_graph.return_value = graph_data + + result = node_manager.get_node() + + assert result == sample_node + mock_adapter.graph.get_graph.assert_called_once_with(node_manager.project) + + def test_get_node_not_found(self, node_manager, mock_adapter): + """Test node not found error.""" + graph_data = GraphData(nodes=[], edges=[]) + mock_adapter.graph.get_graph.return_value = graph_data + + with pytest.raises(ValueError, match="Node with ID 'test-node-001' not found"): + node_manager.get_node() + + def test_exists_true(self, node_manager, mock_adapter, sample_node): + """Test exists returns True when node exists.""" + graph_data = GraphData(nodes=[sample_node], edges=[]) + mock_adapter.graph.get_graph.return_value = graph_data + + assert node_manager.exists() is True + + def test_exists_false(self, node_manager, mock_adapter): + """Test exists returns False when node doesn't exist.""" + graph_data = GraphData(nodes=[], edges=[]) + mock_adapter.graph.get_graph.return_value = graph_data + + assert node_manager.exists() is False + + def test_delete(self, node_manager, mock_adapter): + """Test node deletion.""" + node_manager.delete() + + mock_adapter.graph.delete_node.assert_called_once_with(node_manager.project, "test-node-001") + + def test_update_position(self, node_manager, mock_adapter, sample_node): + """Test node position update.""" + graph_data = GraphData(nodes=[sample_node], edges=[]) + mock_adapter.graph.get_graph.return_value = graph_data + mock_adapter.graph.update_node_position.return_value = sample_node + + result = node_manager.update_position(300.0, 400.0) + + assert result == sample_node + assert sample_node.position.x == 300.0 + assert sample_node.position.y == 400.0 + mock_adapter.graph.update_node_position.assert_called_once() + + @patch("fluidize.managers.node.nodeMetadata_simulation") + def test_get_metadata(self, mock_metadata_class, node_manager): + """Test getting node metadata.""" + mock_metadata = Mock() + mock_metadata_class.from_file.return_value = mock_metadata + + result = node_manager.get_metadata() + + assert result == mock_metadata + mock_metadata_class.from_file.assert_called_once() + + @patch("fluidize.managers.node.nodeProperties_simulation") + def test_get_properties(self, mock_properties_class, node_manager): + """Test getting node properties.""" + mock_properties = Mock() + mock_properties_class.from_file.return_value = mock_properties + + result = node_manager.get_properties() + + assert result == mock_properties + mock_properties_class.from_file.assert_called_once() + + @patch("fluidize.managers.node.nodeParameters_simulation") + def test_get_parameters_model(self, mock_parameters_class, node_manager): + """Test getting node parameters model.""" + mock_parameters = Mock() + mock_parameters.parameters = [] + mock_parameters_class.from_file.return_value = mock_parameters + + result = node_manager.get_parameters_model() + + assert result == mock_parameters + mock_parameters_class.from_file.assert_called_once() + + @patch("fluidize.managers.node.nodeParameters_simulation") + def test_get_parameters(self, mock_parameters_class, node_manager): + """Test getting node parameters list.""" + mock_parameter = Parameter( + value="test_value", description="Test parameter", type="text", label="Test", name="test_param" + ) + mock_parameters = Mock() + mock_parameters.parameters = [mock_parameter] + mock_parameters_class.from_file.return_value = mock_parameters + + result = node_manager.get_parameters() + + assert result == [mock_parameter] + + def test_get_parameter_found(self, node_manager): + """Test getting a specific parameter by name.""" + mock_parameter = Parameter( + value="test_value", description="Test parameter", type="text", label="Test", name="test_param" + ) + + with patch.object(node_manager, "get_parameters", return_value=[mock_parameter]): + result = node_manager.get_parameter("test_param") + assert result == mock_parameter + + def test_get_parameter_not_found(self, node_manager): + """Test getting a parameter that doesn't exist.""" + with patch.object(node_manager, "get_parameters", return_value=[]): + result = node_manager.get_parameter("nonexistent") + assert result is None + + def test_validate_all_valid(self, node_manager, sample_node): + """Test validation when all components are valid.""" + + with ( + patch.object(node_manager, "get_node", return_value=sample_node), + patch.object(node_manager, "get_metadata"), + patch.object(node_manager, "get_properties"), + patch.object(node_manager, "get_parameters", return_value=[]), + ): + result = node_manager.validate() + + assert result["valid"] is True + assert result["graph_node_exists"] is True + assert result["metadata_exists"] is True + assert result["properties_exists"] is True + assert result["parameters_exists"] is True + assert len(result["errors"]) == 0 + + def test_validate_with_errors(self, node_manager): + """Test validation when there are errors.""" + with ( + patch.object(node_manager, "get_node", side_effect=ValueError("Node not found")), + patch.object(node_manager, "get_metadata", side_effect=FileNotFoundError("Metadata not found")), + patch.object(node_manager, "get_properties"), + patch.object(node_manager, "get_parameters", return_value=[]), + ): + result = node_manager.validate() + + assert result["valid"] is False + assert result["graph_node_exists"] is False + assert result["metadata_exists"] is False + assert len(result["errors"]) == 2 + assert "Node not found" in result["errors"][0] + assert "Metadata error: Metadata not found" in result["errors"][1] diff --git a/tests/unit/managers/test_project_graph.py b/tests/unit/managers/test_project_graph.py index b419de6..f411022 100644 --- a/tests/unit/managers/test_project_graph.py +++ b/tests/unit/managers/test_project_graph.py @@ -9,6 +9,7 @@ from fluidize.core.types.parameters import Parameter from fluidize.core.types.runs import RunStatus from fluidize.managers.graph import GraphManager +from fluidize.managers.node import NodeManager from tests.fixtures.sample_graphs import SampleGraphs from tests.fixtures.sample_projects import SampleProjects @@ -94,7 +95,8 @@ def test_add_node_success(self, project_graph, mock_adapter): result = project_graph.add_node(node) - assert result == node + assert isinstance(result, NodeManager) + assert result.node_id == node.id mock_adapter.graph.insert_node.assert_called_once_with( project_graph.project, node, @@ -108,7 +110,8 @@ def test_add_node_with_sim_global_false(self, project_graph, mock_adapter): result = project_graph.add_node(node, sim_global=False) - assert result == node + assert isinstance(result, NodeManager) + assert result.node_id == node.id mock_adapter.graph.insert_node.assert_called_once_with(project_graph.project, node, False) def test_add_node_from_scratch_success(self, project_graph, mock_adapter): @@ -142,7 +145,8 @@ def test_add_node_from_scratch_success(self, project_graph, mock_adapter): result = project_graph.add_node_from_scratch(node, node_properties, node_metadata) - assert result == node + assert isinstance(result, NodeManager) + assert result.node_id == node.id mock_adapter.graph.insert_node_from_scratch.assert_called_once_with( project_graph.project, node, @@ -175,7 +179,8 @@ def test_add_node_from_scratch_with_repo_link(self, project_graph, mock_adapter) result = project_graph.add_node_from_scratch(node, node_properties, node_metadata, repo_link) - assert result == node + assert isinstance(result, NodeManager) + assert result.node_id == node.id mock_adapter.graph.insert_node_from_scratch.assert_called_once_with( project_graph.project, node, node_properties, node_metadata, repo_link ) @@ -344,7 +349,8 @@ def test_add_node_from_scratch_delegates_to_adapter(self, project_graph, mock_ad result = project_graph.add_node_from_scratch(node, node_properties, node_metadata) - assert result == node + assert isinstance(result, NodeManager) + assert result.node_id == node.id mock_adapter.graph.insert_node_from_scratch.assert_called_once_with( project_graph.project, node, node_properties, node_metadata, None ) From 5d020826574bd5e40125fa354052012d74b650f2 Mon Sep 17 00:00:00 2001 From: Henry Bae <69275685+BaeHenryS@users.noreply.github.com> Date: Fri, 15 Aug 2025 15:51:59 -0700 Subject: [PATCH 2/3] Repr removed --- tests/unit/managers/test_project.py | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/tests/unit/managers/test_project.py b/tests/unit/managers/test_project.py index a047284..aa79276 100644 --- a/tests/unit/managers/test_project.py +++ b/tests/unit/managers/test_project.py @@ -186,23 +186,6 @@ def test_to_dict_with_timestamps(self, mock_adapter): assert result["created_at"] == "2024-01-01T00:00:00Z" assert result["updated_at"] == "2024-01-01T12:00:00Z" - def test_repr(self, project_wrapper, sample_project_summary): - """Test __repr__ method.""" - result = repr(project_wrapper) - expected = f"Project(id='{sample_project_summary.id}', label='{sample_project_summary.label}')" - assert result == expected - - def test_repr_with_none_label(self, mock_adapter): - """Test __repr__ method when label is None.""" - minimal_summary = SampleProjects.minimal_project() - project = ProjectManager(mock_adapter, minimal_summary) - - result = repr(project) - # Handle case where minimal project might have label=None or no label attribute - label_value = getattr(minimal_summary, "label", None) - expected = f"Project(id='{minimal_summary.id}', label='{label_value}')" - assert result == expected - def test_str_with_label(self, project_wrapper, sample_project_summary): """Test __str__ method with label.""" result = str(project_wrapper) @@ -287,10 +270,7 @@ def test_wrapper_with_different_project_types(self, mock_adapter, project_fixtur graph = project.graph assert isinstance(graph, GraphManager) - # String representations should work - repr_result = repr(project) + # String representation should work str_result = str(project) - assert isinstance(repr_result, str) assert isinstance(str_result, str) - assert project_summary.id in repr_result assert project_summary.id in str_result From dbc869e13cf6f5111cb3e6a817ee75ea2d86b76a Mon Sep 17 00:00:00 2001 From: Henry Bae <69275685+BaeHenryS@users.noreply.github.com> Date: Fri, 15 Aug 2025 15:57:34 -0700 Subject: [PATCH 3/3] Documentation --- docs/core-modules/client.md | 6 ++++-- docs/core-modules/graph.md | 21 +++++++++++++-------- docs/core-modules/projects.md | 6 ++++-- docs/core-modules/run.md | 9 ++++++--- docs/index.md | 4 ++-- mkdocs.yml | 5 +++-- 6 files changed, 32 insertions(+), 19 deletions(-) diff --git a/docs/core-modules/client.md b/docs/core-modules/client.md index 4c0feb8..2c9bf16 100644 --- a/docs/core-modules/client.md +++ b/docs/core-modules/client.md @@ -9,8 +9,9 @@ The Fluidize Client is the primary interface to create and edit projects. There ::: fluidize.client.FluidizeClient options: show_source: false - show_root_heading: true heading_level: 3 + extra: + show_root_heading: true members: - mode - adapters @@ -20,8 +21,9 @@ The Fluidize Client is the primary interface to create and edit projects. There ::: fluidize.config.FluidizeConfig options: show_source: false - show_root_heading: true heading_level: 3 + extra: + show_root_heading: true members: - is_local_mode - is_api_mode diff --git a/docs/core-modules/graph.md b/docs/core-modules/graph.md index 0f06a45..a7d26d0 100644 --- a/docs/core-modules/graph.md +++ b/docs/core-modules/graph.md @@ -4,8 +4,9 @@ ::: fluidize.managers.graph.GraphManager options: show_source: false - how_root_heading: true heading_level: 3 + extra: + show_root_heading: true members: - get - add_node @@ -18,27 +19,31 @@ ::: fluidize.core.modules.graph.GraphProcessor options: show_source: false - show_root_heading: true heading_level: 3 + extra: + show_root_heading: true ## Graph Types ::: fluidize.core.types.graph.GraphData options: - show_attributes: true - show_root_heading: true heading_level: 3 + extra: + show_attributes: true + show_root_heading: true ::: fluidize.core.types.graph.GraphNode options: - show_attributes: true - show_root_heading: true heading_level: 3 + extra: + show_attributes: true + show_root_heading: true ::: fluidize.core.types.graph.GraphEdge options: - show_attributes: true - show_root_heading: true heading_level: 3 + extra: + show_attributes: true + show_root_heading: true diff --git a/docs/core-modules/projects.md b/docs/core-modules/projects.md index 5c97442..e51384c 100644 --- a/docs/core-modules/projects.md +++ b/docs/core-modules/projects.md @@ -4,12 +4,14 @@ ::: fluidize.managers.registry.RegistryManager options: show_source: false - show_root_heading: true heading_level: 3 + extra: + show_root_heading: true ## Project ::: fluidize.managers.project.ProjectManager options: show_source: false - show_root_heading: true heading_level: 3 + extra: + show_root_heading: true diff --git a/docs/core-modules/run.md b/docs/core-modules/run.md index 00d276a..c21122f 100644 --- a/docs/core-modules/run.md +++ b/docs/core-modules/run.md @@ -5,8 +5,9 @@ ::: fluidize.managers.runs.RunsManager options: show_source: false - show_root_heading: true heading_level: 3 + extra: + show_root_heading: true members: - run_flow - list @@ -17,11 +18,13 @@ ::: fluidize.core.modules.run.RunJob options: show_source: false - show_root_heading: true heading_level: 3 + extra: + show_root_heading: true ::: fluidize.core.modules.run.project.ProjectRunner options: show_source: false - show_root_heading: true heading_level: 3 + extra: + show_root_heading: true diff --git a/docs/index.md b/docs/index.md index dc9c945..93c2428 100644 --- a/docs/index.md +++ b/docs/index.md @@ -41,7 +41,7 @@ make install ## Run Examples -Example projects are located in this folder: [examples/](examples/). There you can find an [Jupyter Notebook](examples/demo.ipynb) of a simple simulation +Example projects are located in this folder: [examples/](https://github.com/Fluidize-Inc/fluidize-python/tree/main/examples). There you can find an [Jupyter Notebook](https://github.com/Fluidize-Inc/fluidize-python/blob/main/examples/demo.ipynb) of a simple simulation ## Architecture @@ -85,7 +85,7 @@ Pipelines can be executed both locally and on the cloud. Local execution is hand ## Contributing -We would love to collaborate with you! Please see our [Contributing Guide](CONTRIBUTING.md) for details. +We would love to collaborate with you! Please see our [Contributing Guide](https://github.com/Fluidize-Inc/fluidize-python/blob/main/CONTRIBUTING.md) for details. Also - we would love to help streamline your pipeline! Please reach out to us at [founders@fluidize.ai](mailto:founders@fluidize.ai). diff --git a/mkdocs.yml b/mkdocs.yml index fc8d77e..ba159d6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -29,12 +29,13 @@ plugins: show_signature_annotations: true members_order: source docstring_style: google - show_attributes: true filters: - "!^_" show_source: false - show_root_heading: true show_root_full_path: false + extra: + show_attributes: true + show_root_heading: true theme: name: material features: