Commit 153d49f3 authored by Ayres, Andrew's avatar Ayres, Andrew
Browse files

Merge branch 'Source-consistency' into 'main'

unifying code source paths

See merge request !27
parents a271a33c 8bdebb5c
Loading
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -96,7 +96,7 @@ A comprehensive list of tools, and links to their XML, can be found in Calvera's

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.

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

To get started, let\'s create the Fractal class. Create an empty file at `src/nova_tutorial/app/models/fractal.py`. Add the following pieces of code to the newly created file.

@@ -145,7 +145,7 @@ Note that we create a `Tool` object with the `id="neutrons_fractal"`. This tells

The line `data_store.persist()` saves your datastore after the "with" block is exited. Without calling this method, all tools, running or finished, along with their results will be discarded after the "with" block finishes execution.

**2. `main.py` - Calling the Model (`src/nova_tutorial/app/main.py`):**
**2. `main.py` - Calling the Model (`src/nova_tutorial/app/main.py`) (Modify):**

We are now going to modify the existing `main.py` file. Change the main method to match the code below.

+8 −7
Original line number Diff line number Diff line
@@ -146,7 +146,7 @@ InputField(v_model="config.username")

Let\'s see how to implement the MVVM pattern using `nova-mvvm` and incorporate Pydantic for data validation.

**1. Adding Fractal to the ViewModel (`src/nova_tutorial/app/view_models/main.py`):**
**1. Adding Fractal to the ViewModel (`src/nova_tutorial/app/view_models/main.py`) (Modify):**

*   **Running our Model**:  We start by adding a method to bottom of our ViewModel which will run the Fractal tool.

@@ -156,7 +156,7 @@ Let\'s see how to implement the MVVM pattern using `nova-mvvm` and incorporate P
        self.update_view()
```

**2. Updating our Fractal Class for pydantaic and MVVM (`src/nova_tutorial/app/models/fractal.py`)**
**2. Updating our Fractal Class for pydantaic and MVVM (`src/nova_tutorial/app/models/fractal.py`) (Modify)**

*   **Adding new imports**: We need to add some imports for pydantic and working with base64 encodings to deal with the image. Modify your import block to match below.

@@ -191,7 +191,7 @@ class Fractal(BaseModel):
                self.image_data = f"data:image/png;base64,{b64encode(image_file.read()).decode()}"
```

**3. Updating our MainModel Class to add the new Fractal Class (`src/nova_tutorial/app/models/main_model.py`)**
**3. Updating our MainModel Class to add the new Fractal Class (`src/nova_tutorial/app/models/main_model.py`) (Modify):**

*   **Add Fractal to imports**: Add an import for the Fractal class into our MainModel.

@@ -206,7 +206,7 @@ from .fractal import Fractal # Import Fractal
    fractal: Fractal = Field(default_factory=Fractal) #Add Fractal Model
```

**4. Creating a FractalTab (`src/nova_tutorial/app/views/fractal_tab.py`):**
**4. Creating a FractalTab (`src/nova_tutorial/app/views/fractal_tab.py`) (Create):**

*   **Create a fractal tab**: Create a new file and add the following code:

@@ -230,7 +230,7 @@ class FractalTab:
        vuetify.VImg(src=("config.fractal.image_data",), height="400", width="400")
```

**5. Modify the tab panel (`src/nova_tutorial/app/views/tabs_panel.py`):**
**5. Modify the tab panel (`src/nova_tutorial/app/views/tabs_panel.py`) (Modify):**

*   **Add Fractal Tab to the tab panel**: Modify the tab panel to add our new Fractal tab

@@ -241,7 +241,7 @@ class FractalTab:
            vuetify.VTab("Sample Tab 2", value=3)
```

**6. Modify the tab panel content (`src/nova_tutorial/app/views/tab_content_panel.py`):**
**6. Modify the tab panel content (`src/nova_tutorial/app/views/tab_content_panel.py`) (Modify):**

*   **Add FractalTab to imports**: Import the newly created FractalTab class into our tab_content_panel.

@@ -261,7 +261,7 @@ from .fractal_tab import FractalTab # Import the FractalTab
                            SampleTab2()
```

**7. `main.py` - Calling the Model (`src/nova_tutorial/app/main.py`):**
**7. `main.py` - Calling the Model (`src/nova_tutorial/app/main.py`) (Modify):**

We are now going to modify the existing `main.py` file. Change the main method to match the code below.

@@ -303,6 +303,7 @@ If you don't want Trame to launch a tab by default, you can instead run ```poetr

:::::::::::::::::::::::::::::::::::::::  challenge
**Trigger Pydantic Validation Error (Programmatic)**

*   In `Fractal` in `src/nova_tutorial/app/models/fractal.py`, modify the `set_fractal_type` function from the previous exercise to use an *invalid* fractal type:

```python
+6 −6
Original line number Diff line number Diff line
@@ -75,7 +75,7 @@ Layouts are responsible for arraging your content in a consistent manner. In Tra

`nova-trame` provides a basic layout and theme that you can access via the `ThemedApp` class. The template app will setup your main view class to inherit from `ThemedApp` already, so let\'s look at how the layout is defined and how we can add content to slots.

**1. `nova_tutorial/app/views/main.py`:**
**1. `src/nova_tutorial/app/views/main.py` (Modify):**

```python
class MainApp(ThemedApp):
@@ -215,7 +215,7 @@ For a more detailed explanation of how to work with our layout and theme, please

Now, let\'s add some UI components to the Sample Tabs in our application to demonstrate how to use these components. We\'ll modify the `sample_tab_1.py` and `sample_tab_2.py` files to include these components.

**2. `nova_tutorial/app/views/sample_tab_1.py` (Modify):**
**2. `src/nova_tutorial/app/views/sample_tab_1.py` (Modify):**

We\'ll add an `InputField` and a `VBoxLayout` to this tab.

@@ -241,7 +241,7 @@ class SampleTab1:

Since `config.file` doesn't exist yet, we\'ll need to add it to the model.

**3. `nova_tutorial/app/models/main_model.py` (Modify):**
**3. `src/nova_tutorial/app/models/main_model.py` (Modify):**

```python
    username: str = Field(
@@ -256,7 +256,7 @@ Since `config.file` doesn't exist yet, we\'ll need to add it to the model.
    fractal: Fractal = Field(default_factory=Fractal)
```

**4. `nova_tutorial/app/views/sample_tab_2.py` (Modify):**
**4. `src/nova_tutorial/app/views/sample_tab_2.py` (Modify):**

We\'ll add a `GridLayout` and an `InputField` to this tab.

@@ -295,7 +295,7 @@ You should now see the simple UI. When you click the "Sample Tab 1" and "Sample

Now that we understand the basics of working with Trame, let\'s make the view for the fractal tab a bit more intuitive for the user by giving them a visual indicator that the job is running.

**5. `nova_tutorial/app/views/fractal_tab.py` (Modify):**
**5. `src/nova_tutorial/app/views/fractal_tab.py` (Modify):**

```python
    def __init__(self, view_model: MainViewModel) -> None:
@@ -316,7 +316,7 @@ Now that we understand the basics of working with Trame, let\'s make the view fo

We will need to add a data binding for `running`, as well. We choose to place this directly in the view model as this is not relevant to running the fractal tool on NDIP.

**6. `nova_tutorial/app/view_models/main.py` (Modify):**
**6. `src/nova_tutorial/app/view_models/main.py` (Modify):**

```python
    def __init__(self, model: MainModel, binding: BindingInterface):
+19 −11
Original line number Diff line number Diff line
@@ -90,7 +90,7 @@ poetry install

Pydantic uses Python type hints to define data models. When you create an instance of a Pydantic model, Pydantic automatically validates the input data against the defined types and constraints.

Here\'s a simple example (add this code to `src/advanced_pydantic/main.py`):
1. Create a User Model (`src/advanced_pydantic/main.py`) (Modify):

```python
from pydantic import BaseModel, Field
@@ -104,7 +104,7 @@ In this example, we define a `User` model with two fields: `id` and `name`. We u

When you create an instance of the `User` model, Pydantic automatically validates the input data.

Modify the main function in `src/advanced_pydantic/main.py`
2. Create an instance of a User (`src/advanced_pydantic/main.py`) (Modify):

```python
from pydantic import ValidationError
@@ -130,7 +130,7 @@ If the input data is invalid, Pydantic raises a `ValidationError` exception with

When working with structured data, it\'s common to have nested objects. For example, a User model from the above example might have multiple Address entries. In Pydantic, we can achieve this by creating nested models.

1. Creating the Address Model (add code to `src/advanced_pydantic/main.py`).
1. Creating the Address Model (`src/advanced_pydantic/main.py`) (Modify).

The Address model represents a simple address with three fields:

@@ -150,7 +150,7 @@ class Address(BaseModel):
    type: Literal["home", "work"] = Field()
```

2. Using the Address Model as a Nested Field (modify User model in `src/advanced_pydantic/main.py`).
2. Using the Address Model as a Nested Field (`src/advanced_pydantic/main.py`) (Modify).

Update the User model so that it now contains:

@@ -167,7 +167,7 @@ class User(BaseModel):
    addresses: List[Address] = Field(min_length=1)
```

now you can try to test the model. Modify the main function in `src/advanced_pydantic/main.py`
3. Test the model (`src/advanced_pydantic/main.py`) (Modify).

```python    
def main() -> None:
@@ -202,7 +202,9 @@ For easier integration with the NOVA framework, where model field information is

Sometimes, simple validation like checking the minimum length is not enough. In such cases, you can write a custom validation function for a specific field.

For example, let\'s say we have a User model where only even IDs are allowed. We can enforce this constraint using the `@field_validator decorator` (modify `src/advanced_pydantic/main.py`):
For example, let\'s say we have a User model where only even IDs are allowed. We can enforce this constraint using the `@field_validator decorator`.

4. Using the `@field_validator decorator` (`src/advanced_pydantic/main.py`) (Modify):

```python
from pydantic import BaseModel, Field, field_validator
@@ -241,7 +243,9 @@ Note that we used the mode="**after**" option for the validator. This ensures th

In some cases, you may need to validate the entire model, not just individual fields. This can be done by writing a custom validation function for the whole model using the `@model_validator` decorator.

For example, let\'s say we have a User model where the name and id must meet specific conditions together. For instance, we only allow users with even IDs to have names that start with a capital letter. We can enforce this logic using a @model_validator (modify `src/advanced_pydantic/main.py`):
For example, let\'s say we have a User model where the name and id must meet specific conditions together. For instance, we only allow users with even IDs to have names that start with a capital letter. We can enforce this logic using a @model_validator.

5. Using the `@model_validator decorator` (`src/advanced_pydantic/main.py`) (Modify):

```python
from pydantic import BaseModel, Field, model_validator
@@ -327,7 +331,7 @@ poetry install

One of the great features of the NOVA Framework is that it allows an application to leverage Pydantic models to automatically validate UI elements. Let\'s walk through what that looks like in code.

First, let\'s add the following Model (create `src/trame_with_pydantic/app/models/settings.py`):
1. First, let\'s add the following Model (`src/trame_with_pydantic/app/models/settings.py`) (Create):

```python
from pydantic import BaseModel, Field
@@ -336,7 +340,9 @@ class SettingsModel(BaseModel):
    port: int = Field(default=8080, gt=0, lt=65536, title="Port Number", description="The port to listen on.", examples=["12345"])
```

Then in your ViewModel, you createa binding for this Model (modify `src/trame_with_pydantic/app/view_models/main.py` and clean up the code created by the template engine, we don't need it for this example):
2. Create a binding for the model (`src/trame_with_pydantic/app/view_models/main.py`) (Modify):

In your ViewModel, create a binding for this Model and clean up the code created by the template engine, we don't need it for this example):

```python
from typing import Any, Dict
@@ -353,7 +359,7 @@ class MainViewModel:
        self.settings_bind.update_in_view(self.settings)
```

In your view, remove all other fields and add the following InputField (modify `src/trame_with_pydantic/app/views/main.py`):
3. In your view, remove all other fields and add the following InputField (`src/trame_with_pydantic/app/views/main.py`) (Modify):

```python
...
@@ -370,7 +376,9 @@ In that fashion, the `InputField` seamlessly pulls information from your code\'s

### Using callbacks in ViewModel to react to validation errors

Sometimes, you may want to respond to UI validation errors beyond just marking a field as invalid (which happens automatically). In such cases, you can add a callback to the `new_bind` function (modify `src/trame_with_pydantic/app/view_models/main.py`):
Sometimes, you may want to respond to UI validation errors beyond just marking a field as invalid (which happens automatically). In such cases, you can add a callback to the `new_bind` function.

4. Using callbacks with `new_bind` (`src/trame_with_pydantic/app/view_models/main.py`) (modify):

```python
class MainViewModel:
+2 −2
Original line number Diff line number Diff line
@@ -91,7 +91,7 @@ The pandas install is only necessary for loading example data from Plotly, which

Now, we can create a view that displays a Plotly figure.

**1. `PlotlyView` View Class (`src/viz_examples/views/plotly.py`):**
**1. `PlotlyView` View Class (`src/viz_examples/views/plotly.py`) (Create):**

*   **Imports**:  Pay special attention to the plotly import. This module contains a Trame widget that will allow us to quickly add a Plotly chart to our view.

@@ -151,7 +151,7 @@ class PlotlyView:

As with our previous examples, there is a corresponding model.

**2. `PlotlyConfig` Model Class (src/viz_examples/models/plotly.py):**
**2. `PlotlyConfig` Model Class (src/viz_examples/models/plotly.py) (Create):**

*   **Imports**:  The graph_objects module is how we will define the content for our chart. The iris module defines an example dataset.