Commit b9023af3 authored by Ayres, Andrew's avatar Ayres, Andrew
Browse files

fix apostrophes

parent 8c6405f0
Loading
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ The Neutron Data Interpretation Platform (NDIP) is a workflow management system

NOVA is a framework that aims to simplify the development of applications that interact with NDIP. It consists of three core libraries:

*   **`nova-galaxy`**: This library simplifies interaction with the NDIP platform's APIs. It allows developers to easily connect to NDIP, **submit jobs, handle parameters, and monitor job progress.**
*   **`nova-galaxy`**: This library simplifies interaction with the NDIP platform\'s APIs. It allows developers to easily connect to NDIP, **submit jobs, handle parameters, and monitor job progress.**

*   **`nova-trame`**: This library facilitates the creation of interactive user interfaces using Trame. Trame is a powerful Python framework for building web-based UIs and visualizations. `nova-trame` provides a consistent look and feel for NOVA applications by simplifying interactions with Trame components (such as Vuetify).

@@ -50,13 +50,13 @@ In this tutorial, you will learn how to use these three core NOVA libraries to b
*   Submit jobs to NDIP.
*   Monitor the progress of your submitted jobs.

We'll be using example tools from neutron scattering as a demonstration for this tutorial, however, the lessons learned here can be applied to a wide variety of neutron analysis applications. This hands-on tutorial will guide you through each step of the process, empowering you to build your own interactive tools for neutron data analysis and management.
We\'ll be using example tools from neutron scattering as a demonstration for this tutorial, however, the lessons learned here can be applied to a wide variety of neutron analysis applications. This hands-on tutorial will guide you through each step of the process, empowering you to build your own interactive tools for neutron data analysis and management.

## Code Examples Directory

All of the code examples used in this tutorial are available in the `code` directory of the tutorial repository. These examples are built upon the template application that you will clone in the next episode. The code is organized by episode, with each episode having its own subdirectory (e.g., `code/episode_2`, `code/episode_3`, etc.).

Each episode's subdirectory contains a complete, self-contained Python project that can be run independently using Poetry. This allows you to easily explore the code examples, run them, and modify them as you go through the tutorial.
Each episode\'s subdirectory contains a complete, self-contained Python project that can be run independently using Poetry. This allows you to easily explore the code examples, run them, and modify them as you go through the tutorial.

::::::::::::::::::::::::::::::::::::::::: callout

@@ -64,7 +64,7 @@ Poetry is a tool for dependency management and packaging in Python. It allows yo

::::::::::::::::::::::::::::::::::::::::::::::::::

To run the code for a specific episode, navigate to the episode's directory in your terminal and use the following commands:
To run the code for a specific episode, navigate to the episode\'s directory in your terminal and use the following commands:

```bash
cd code/episode_X  # Replace X with the episode number
+5 −5
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@ exercises: 3
As mentioned in the introduction, all code examples in this tutorial are based on a template application. In this episode, we will create this starting point by cloning a template using the `copier` library. This template provides a basic project structure and pre-configured files that will help us get started quickly with our NOVA project, saving us from setting up everything from scratch.

::::::::::::::::::::::::::::::::::::::::: callout
The setup section detailed the prerequisites required for the tutorial. One of those prerequisites is copier which will be used to clone a template application. If you've not already insalled copier and other dependencies, please follow the instructions in the "Setup" section.
The setup section detailed the prerequisites required for the tutorial. One of those prerequisites is copier which will be used to clone a template application. If you\'ve not already insalled copier and other dependencies, please follow the instructions in the "Setup" section.

::::::::::::::::::::::::::::::::::::::::::::::::::

@@ -65,10 +65,10 @@ The template creates a basic project structure to help get you started quickly.
*   `nova_tutorial/`: The root directory of your project
*   `nova_tutorial/src/`: Contains your application code
*   `nova_tutorial/src/nova_tutorial/`: The name of your python package
*   `nova_tutorial/tests/`: Contains your application's unit tests.
*   `nova_tutorial/tests/`: Contains your application\'s unit tests.
*   `nova_tutorial/README.md`: A readme file.

**Note:** The code provided in the `code/episode_2` directory represents a simplified version of the template output, focused on the essential files for this tutorial. The full template, as generated by `copier`, includes additional configuration files (like Dockerfiles and CI setup) that are not strictly necessary for following the tutorial's core concepts.
**Note:** The code provided in the `code/episode_2` directory represents a simplified version of the template output, focused on the essential files for this tutorial. The full template, as generated by `copier`, includes additional configuration files (like Dockerfiles and CI setup) that are not strictly necessary for following the tutorial\'s core concepts.

In the following sections, we will start adding code to this structure to build our NDIP job submission tool.

@@ -103,7 +103,7 @@ The template includes pre-commit hooks for code formatting and linting.
        *   `isort`: Sorts Python imports alphabetically and separates them into sections.
        *   `end-of-file-fixer`: Ensures that files end with a newline.
        *   `trailing-whitespace-fixer`: Removes trailing whitespace from lines.
    *   **Observe how the pre-commit hooks automatically fix the formatting issues:** When you run `pre-commit run`, the configured tools will automatically modify the files to correct formatting errors. The output will show which tools were run and which files were modified. You'll need to `git add` the modified files before committing.
    *   **Observe how the pre-commit hooks automatically fix the formatting issues:** When you run `pre-commit run`, the configured tools will automatically modify the files to correct formatting errors. The output will show which tools were run and which files were modified. You\'ll need to `git add` the modified files before committing.

:::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::
@@ -112,7 +112,7 @@ The template includes pre-commit hooks for code formatting and linting.

## **CI/CD Setup with GitLab CI**

The template includes a basic GitLab CI configuration file (`.gitlab-ci.yml`).  While we won't fully execute a CI/CD pipeline in this tutorial step, let's understand its purpose.
The template includes a basic GitLab CI configuration file (`.gitlab-ci.yml`).  While we won\'t fully execute a CI/CD pipeline in this tutorial step, let\'s understand its purpose.
    *   **Examine the Configuration:** Open the `.gitlab-ci.yml` file.  This file defines the *pipeline*.  What are the key stages defined in the pipeline (e.g., build, test, deploy)?  Identify the jobs that install dependencies, run tests, and perform linting. What triggers the pipeline to run (e.g., pushes, merge requests)?
    *   **GitLab Runner:** GitLab CI/CD uses *runners* to execute the jobs defined in your `.gitlab-ci.yml` file.  These runners can be configured in various ways.  (No action required; this is just an informational point.)
    *   **Discussion:** If you were to push this project to a GitLab repository, what would happen when you create a merge request? How could you use CI/CD to automatically verify the code quality of your project? (No action required; this is a thought exercise.)
+6 −4
Original line number Diff line number Diff line
@@ -24,11 +24,11 @@ The basic workflow for running a tool with `nova-galaxy` involves these steps:
1.  **Connect to NDIP**: Create a `Nova` instance with your credentials.
2.  **Define the Tool**: Create a `Tool` instance, specifying the ID of the NDIP tool you want to run.
3.  **Set Parameters**: Create a `Parameters` instance and add the necessary input parameters and their values for the tool.
4.  **Run the Tool**: Use the `tool.run()` method to submit the job to NDIP. This typically involves creating a datastore to hold the job's input and output data.
4.  **Run the Tool**: Use the `tool.run()` method to submit the job to NDIP. This typically involves creating a datastore to hold the job\'s input and output data.

## Creating a Model of a Tool

Let's create a `Fractal` class that uses `nova-galaxy` to run the `neutrons_fractal` tool on NDIP. You can find the complete code for this episode in the `code/episode_3` directory. Here, we will focus on the key code snippets and explain the important parts.
Let\'s create a `Fractal` class that uses `nova-galaxy` to run the `neutrons_fractal` tool on NDIP. You can find the complete code for this episode in the `code/episode_3` directory. Here, we will focus on the key code snippets and explain the important parts.

**1. `Fractal` Class (`src/nova_tutorial/models/fractal.py`):**

@@ -48,7 +48,7 @@ Let's create a `Fractal` class that uses `nova-galaxy` to run the `neutrons_frac
            self.galaxy_key = os.getenv("GALAXY_API_KEY")
    ```

*   **`run_fractal_tool` method**: This method encapsulates the logic for running the `fractal` tool. Let's examine the key steps within this method:
*   **`run_fractal_tool` method**: This method encapsulates the logic for running the `fractal` tool. Let\'s examine the key steps within this method:

    *   **Instantiate `Nova`, `Tool`, and `Parameters`**: We create instances of the `Nova`, `Tool`, and `Parameters` classes:
        ```python
@@ -58,7 +58,7 @@ Let's create a `Fractal` class that uses `nova-galaxy` to run the `neutrons_frac
            params.add_input(name="fractal_type", value=self.fractal_type)

        ```
        Note that we create a `Tool` object with the `id="neutrons_fractal"`. This tells `nova-galaxy` which NDIP tool we want to run. The obvious question at this point is how do we know the id of the tool and what parameters it expects? We can look at the tool's launch page in calvera for some hints but ultimately we have to look at the tool's [xml file](https://code.ornl.gov/ndip/galaxy-tools/-/blob/dev/tools/neutrons/test_tools/fractal.xml?ref_type=heads). 
        Note that we create a `Tool` object with the `id="neutrons_fractal"`. This tells `nova-galaxy` which NDIP tool we want to run. The obvious question at this point is how do we know the id of the tool and what parameters it expects? We can look at the tool\'s launch page in calvera for some hints but ultimately we have to look at the tool\'s [xml file](https://code.ornl.gov/ndip/galaxy-tools/-/blob/dev/tools/neutrons/test_tools/fractal.xml?ref_type=heads). 

    *   **Connect and Run the Tool**:  The `with nova.connect() as galaxy_connection:` block establishes a connection to NDIP and ensures proper handling of the connection:
        ```python
@@ -108,6 +108,7 @@ In this section, you learned how to use the `nova-galaxy` library to run a tool
:::::::::::::::::::::::::::::::::::::::  challenge
**Run with Different Fractal Types** 
Modify the `FractalViewModel` class to default to a different fractal type (e.g., "julia"). Run the application again and verify that it still works.

:::::::::::::::  solution
The simplest way to accomplish this is to change the default for fractal type in the Fractal class. You can easily observe the change in galaxy.
:::::::::::::::::::::::::
@@ -116,6 +117,7 @@ The simplest way to accomplish this is to change the default for fractal type in
:::::::::::::::::::::::::::::::::::::::  challenge
**Introduce an Error**
Introduce an error into the code by changing the tool id to something different. What ouput do you see? What if you change the fractal_type to an invalid option such as mandel instead of mandelbrot?

:::::::::::::::  solution
In both cases, an error is received from the ndip-galaxy library. When changing the tool id, a `Tool not found` error will be returned. When selecting an invalid parameter, a `parameter 'option': an invalid option was selected` error will be returned.
:::::::::::::::::::::::::
+16 −16
Original line number Diff line number Diff line
@@ -6,11 +6,11 @@ exercises: 0

# 4. User Interface Best Practices: The MVVM Design Pattern

In this section, we will introduce the Model-View-ViewModel (MVVM) design pattern, a powerful architectural approach for structuring applications, particularly those with user interfaces. We'll explore the core principles of MVVM, the roles of each component, and how the NOVA framework simplifies its implementation, making your code more organized, testable, and maintainable.
In this section, we will introduce the Model-View-ViewModel (MVVM) design pattern, a powerful architectural approach for structuring applications, particularly those with user interfaces. We\'ll explore the core principles of MVVM, the roles of each component, and how the NOVA framework simplifies its implementation, making your code more organized, testable, and maintainable.

## What is a Design Pattern?

Before diving into MVVM, it's helpful to understand what a *design pattern* is in software development. A design pattern is a reusable solution to a commonly occurring problem in software design. It's not a code snippet you can copy and paste, but rather a template or blueprint for how to structure your code to achieve a specific goal (e.g., separation of concerns, code reusability, testability).
Before diving into MVVM, it\'s helpful to understand what a *design pattern* is in software development. A design pattern is a reusable solution to a commonly occurring problem in software design. It\'s not a code snippet you can copy and paste, but rather a template or blueprint for how to structure your code to achieve a specific goal (e.g., separation of concerns, code reusability, testability).

## The Model-View-ViewModel (MVVM) Pattern

@@ -18,32 +18,32 @@ MVVM is an architectural design pattern specifically designed for applications w

The MVVM pattern consists of three core components:

*   **Model:** The Model represents the *data* and the *business logic* of the application. It's responsible for:
*   **Model:** The Model represents the *data* and the *business logic* of the application. It\'s responsible for:
    *   Data storage (e.g., reading from and writing to a database, a file, or an API).
    *   Data validation (ensuring the data is in a valid state).
    *   Business rules (the logic that governs how the data is manipulated and used).

    The Model is *agnostic* to the UI. It doesn't know anything about how the data will be displayed or how the user will interact with it. It simply provides the data and the means to manipulate it.
    The Model is *agnostic* to the UI. It doesn\'t know anything about how the data will be displayed or how the user will interact with it. It simply provides the data and the means to manipulate it.

    *In the context of our NOVA tutorial, the Model will often include the logic for interacting with the NDIP platform via `nova-galaxy`.*

*   **View:** The View is the *user interface* (UI) of the application. It's responsible for:
*   **View:** The View is the *user interface* (UI) of the application. It\'s responsible for:
    *   Displaying data to the user.
    *   Capturing user input (e.g., button clicks, text entered in a field, selections from a dropdown).
    *   Presenting the application's visual appearance.
    *   Presenting the application\'s visual appearance.

    The View is *passive*. It doesn't contain any business logic or data manipulation code. It simply displays the data provided to it and relays user actions to the ViewModel.
    The View is *passive*. It doesn\'t contain any business logic or data manipulation code. It simply displays the data provided to it and relays user actions to the ViewModel.

    *In our NOVA tutorial, the View will be built using Trame and Vuetify components, leveraging the styling and structure provided by `nova-trame`.*

*   **ViewModel:** The ViewModel acts as an *intermediary* between the Model and the View. It's responsible for:
*   **ViewModel:** The ViewModel acts as an *intermediary* between the Model and the View. It\'s responsible for:
    *   Preparing data from the Model for display in the View. This might involve formatting the data, combining data from multiple sources, or creating derived data.
    *   Handling user actions from the View. This might involve validating user input, updating the Model, or triggering other actions in the application.
    *   Exposing data and commands to the View through *data binding*.

    The ViewModel is *UI-specific*. It knows about the View and the data that the View needs, but it doesn't know about the specific UI elements that are used to display the data. It also orchestrates the interaction between the View and the Model.
    The ViewModel is *UI-specific*. It knows about the View and the data that the View needs, but it doesn\'t know about the specific UI elements that are used to display the data. It also orchestrates the interaction between the View and the Model.

    *The ViewModel is where we'll use `nova-mvvm` to create bindings between the ViewModel and the View, enabling the reactive updates.*
    *The ViewModel is where we\'ll use `nova-mvvm` to create bindings between the ViewModel and the View, enabling the reactive updates.*

## Why Use MVVM? (Benefits)

@@ -53,7 +53,7 @@ The MVVM pattern provides several benefits:
*   **Testability:** Because the ViewModel is independent of the View, it can be easily unit-tested. You can test the presentation logic without needing to create a UI.
*   **Maintainability:** Changes to the UI are less likely to affect the underlying application logic, and vice versa. This makes the application easier to maintain and evolve over time.
*   **Reusability:** The ViewModel can be reused with different Views, allowing you to create different UIs for the same underlying data and logic.
*   **Team Collaboration:** MVVM facilitates collaboration between developers and UI designers. Developers can focus on the Model and ViewModel, while designers can focus on the View, without interfering with each other's work.
*   **Team Collaboration:** MVVM facilitates collaboration between developers and UI designers. Developers can focus on the Model and ViewModel, while designers can focus on the View, without interfering with each other\'s work.

## Data Binding: The Heart of MVVM

@@ -82,7 +82,7 @@ Benefits of Pydantic:

## Implementing MVVM with `nova-mvvm` and Pydantic - Key Code Snippets

Let's see how to implement the MVVM pattern using `nova-mvvm` and incorporate Pydantic for data validation in our `FractalViewModel`. You can find the complete code for this episode in the `code/episode_4` directory. Here, we will focus on the key code snippets and explain the important parts.
Let\'s see how to implement the MVVM pattern using `nova-mvvm` and incorporate Pydantic for data validation in our `FractalViewModel`. You can find the complete code for this episode in the `code/episode_4` directory. Here, we will focus on the key code snippets and explain the important parts.

**1. `FractalToolInput` Pydantic Model (`src/nova_tutorial/view_models/fractal_view_model.py`):**

@@ -95,7 +95,7 @@ Let's see how to implement the MVVM pattern using `nova-mvvm` and incorporate Py
    class FractalToolInput(BaseModel):
        fractal_type: Literal["mandelbrot", "julia", "random", "markus"]
    ```
    By defining `fractal_type` with `Literal[...]`, we ensure that only the specified string values are accepted, leveraging Pydantic's data validation capabilities.
    By defining `fractal_type` with `Literal[...]`, we ensure that only the specified string values are accepted, leveraging Pydantic\'s data validation capabilities.

**2. `FractalViewModel` Class (`src/nova_tutorial/view_models/fractal_view_model.py`):**

@@ -146,9 +146,9 @@ Let's see how to implement the MVVM pattern using `nova-mvvm` and incorporate Py
            self._fractal_type = fractal_type
            self.fractal_type_bind.update_in_view(self._fractal_type) # Update fractal_type state
    ```
    Here, `FractalToolInput(fractal_type=fractal_type)` attempts to create an instance of the Pydantic model, which triggers validation. If `fractal_type` is invalid, a `ValidationError` is caught, and the error message is set in the ViewModel's `_message` state, which, thanks to binding, *will later* update the UI.
    Here, `FractalToolInput(fractal_type=fractal_type)` attempts to create an instance of the Pydantic model, which triggers validation. If `fractal_type` is invalid, a `ValidationError` is caught, and the error message is set in the ViewModel\'s `_message` state, which, thanks to binding, *will later* update the UI.

*   **`run_fractal_tool` method**:  In `run_fractal_tool`, we now also use the `message_bind` and `run_button_disabled_bind` to update the UI state (even though we don't have a UI yet, this demonstrates good MVVM practice):
*   **`run_fractal_tool` method**:  In `run_fractal_tool`, we now also use the `message_bind` and `run_button_disabled_bind` to update the UI state (even though we don\'t have a UI yet, this demonstrates good MVVM practice):

    ```python
    def run_fractal_tool(self):
@@ -162,7 +162,7 @@ Let's see how to implement the MVVM pattern using `nova-mvvm` and incorporate Py
            raise e
        self._job_status["fractal"] = "Completed"
    ```
    Even without a View, we are already considering what the functionality that we'll need to support. We've already created the bindings to server as our communicators between our future view and our new view model.
    Even without a View, we are already considering what the functionality that we\'ll need to support. We\'ve already created the bindings to server as our communicators between our future view and our new view model.

**2. `main.py` - Wiring up TrameBinding (`src/nova_tutorial/main.py`):**

+11 −11

File changed.

Preview size limit exceeded, changes collapsed.

Loading