nova-trame 0.19.1__py3-none-any.whl → 0.20.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,109 @@
1
+ """Module for the Progress Tab."""
2
+
3
+ from trame.app import get_server
4
+ from trame.widgets import client
5
+ from trame.widgets import vuetify3 as vuetify
6
+ from trame_client.widgets import html
7
+
8
+ from nova.mvvm.trame_binding import TrameBinding
9
+ from nova.trame.view_model.execution_buttons import ExecutionButtonsViewModel
10
+
11
+
12
+ class ExecutionButtons:
13
+ """Execution buttons class. Adds Run/Stop/Cancel/Download buttons to the view."""
14
+
15
+ def __init__(self, id: str, stop_btn: bool = False, download_btn: bool = False) -> None:
16
+ """Constructor for ExecutionButtons.
17
+
18
+ Parameters
19
+ ----------
20
+ id : str
21
+ Component id. Should be used consistently with ToolRunner and other components
22
+ stop_btn: bool
23
+ Display stop button.
24
+ download_btn : bool
25
+ Display download button.
26
+
27
+ Returns
28
+ -------
29
+ None
30
+ """
31
+ self.id = f"execution_{id}"
32
+
33
+ self.server = get_server(None, client_type="vue3")
34
+ binding = TrameBinding(self.server.state)
35
+ self.ctrl = self.server.controller
36
+ self.stop_btn = stop_btn
37
+ self.download_btn = download_btn
38
+ self.view_model = ExecutionButtonsViewModel(id, binding)
39
+ self.view_model.buttons_state_bind.connect(self.id)
40
+ self._download = client.JSEval(
41
+ exec=(
42
+ "async ($event) => {"
43
+ " const blob = new window.Blob([$event], {type: 'application/zip'});"
44
+ " const url = window.URL.createObjectURL(blob);"
45
+ " const anchor = window.document.createElement('a');"
46
+ " anchor.setAttribute('href', url);"
47
+ " anchor.setAttribute('download', 'results.zip');"
48
+ " window.document.body.appendChild(anchor);"
49
+ " anchor.click();"
50
+ " window.document.body.removeChild(anchor);"
51
+ " setTimeout(() => window.URL.revokeObjectURL(url), 1000);"
52
+ "}"
53
+ )
54
+ ).exec
55
+
56
+ self.create_ui()
57
+
58
+ def create_ui(self) -> None:
59
+ with html.Div(classes="d-flex justify-center my-4 w-100"):
60
+ vuetify.VBtn(
61
+ "Run",
62
+ disabled=(f"{self.id}.run_disabled",),
63
+ prepend_icon="mdi-play",
64
+ classes="mr-4",
65
+ id=f"{self.id}_run",
66
+ click=self.run,
67
+ )
68
+ if self.stop_btn:
69
+ vuetify.VBtn(
70
+ "Stop",
71
+ disabled=(f"{self.id}.stop_disabled",),
72
+ loading=(f"{self.id}.stop_in_progress",),
73
+ classes="mr-4",
74
+ id=f"{self.id}_stop",
75
+ prepend_icon="mdi-stop",
76
+ click=self.stop,
77
+ )
78
+ vuetify.VBtn(
79
+ "Cancel",
80
+ disabled=(f"{self.id}.cancel_disabled",),
81
+ color="error",
82
+ loading=(f"{self.id}.cancel_in_progress",),
83
+ prepend_icon="mdi-cancel",
84
+ classes="mr-4",
85
+ id=f"{self.id}_cancel",
86
+ click=self.cancel,
87
+ )
88
+ if self.download_btn:
89
+ vuetify.VBtn(
90
+ "Download Results",
91
+ disabled=(f"{self.id}.download_disabled",),
92
+ loading=(f"{self.id}.download_in_progress",),
93
+ id=f"{self.id}.download",
94
+ click=self.download,
95
+ )
96
+
97
+ async def download(self) -> None:
98
+ content = await self.view_model.prepare_results()
99
+ if content:
100
+ self._download(content)
101
+
102
+ async def run(self) -> None:
103
+ await self.view_model.run()
104
+
105
+ async def cancel(self) -> None:
106
+ await self.view_model.cancel()
107
+
108
+ async def stop(self) -> None:
109
+ await self.view_model.stop()
@@ -290,7 +290,7 @@ class InputField:
290
290
  )
291
291
  ).exec
292
292
 
293
- @state.change(input.v_model)
293
+ @state.change(input.v_model.split(".")[0])
294
294
  def _autoscroll(**kwargs: Any) -> None:
295
295
  autoscroll(input.id)
296
296
 
@@ -0,0 +1,62 @@
1
+ """Module for the Progress Tab."""
2
+
3
+ from trame.app import get_server
4
+ from trame.widgets import vuetify3 as vuetify
5
+ from trame_client.widgets import html
6
+
7
+ from nova.mvvm.trame_binding import TrameBinding
8
+ from nova.trame.view_model.progress_bar import ProgressBarViewModel
9
+
10
+
11
+ class ProgressBar:
12
+ """Progress bar class. Adds progress bar that displays job status to the view."""
13
+
14
+ def __init__(self, id: str) -> None:
15
+ """Constructor for ProgressBar.
16
+
17
+ Parameters
18
+ ----------
19
+ id : str
20
+ Component id. Should be used consistently with ToolRunner and other components
21
+
22
+ Returns
23
+ -------
24
+ None
25
+ """
26
+ self.id = f"progress_bar_{id}"
27
+ self.create_viewmodel(id)
28
+ self.view_model.progress_state_bind.connect(self.id)
29
+ self.create_ui()
30
+
31
+ def create_viewmodel(self, id: str) -> None:
32
+ server = get_server(None, client_type="vue3")
33
+ binding = TrameBinding(server.state)
34
+ self.view_model = ProgressBarViewModel(id, binding)
35
+
36
+ def create_ui(self) -> None:
37
+ with vuetify.VProgressLinear(
38
+ height="25",
39
+ model_value=(f"{self.id}.progress", "0"),
40
+ striped=True,
41
+ id=f"{self.id}_show_progress",
42
+ v_show=(f"{self.id}.show_progress",),
43
+ ):
44
+ html.H5(v_text=f"{self.id}.details")
45
+ with vuetify.VProgressLinear(
46
+ height="25",
47
+ model_value="100",
48
+ striped=False,
49
+ color="error",
50
+ id=f"{self.id}_show_failed",
51
+ v_show=(f"{self.id}.show_failed",),
52
+ ):
53
+ html.H5(v_text=f"{self.id}.details", classes="text-white")
54
+ with vuetify.VProgressLinear(
55
+ height="25",
56
+ model_value="100",
57
+ striped=False,
58
+ color="primary",
59
+ id=f"{self.id}_show_ok",
60
+ v_show=(f"{self.id}.show_ok",),
61
+ ):
62
+ html.H5(v_text=f"{self.id}.details", classes="text-white")
@@ -0,0 +1,60 @@
1
+ """Module for the Tool outputs."""
2
+
3
+ from trame.app import get_server
4
+ from trame.widgets import vuetify3 as vuetify
5
+
6
+ from nova.mvvm.trame_binding import TrameBinding
7
+ from nova.trame.view.components import InputField
8
+ from nova.trame.view.layouts import HBoxLayout
9
+ from nova.trame.view_model.tool_outputs import ToolOutputsViewModel
10
+
11
+
12
+ class ToolOutputWindows:
13
+ """Tool outputs class. Displays windows with tool stdout/stderr."""
14
+
15
+ def __init__(self, id: str) -> None:
16
+ """Constructor for ToolOutputWindows.
17
+
18
+ Parameters
19
+ ----------
20
+ id : str
21
+ Component id. Should be used consistently with ToolRunner and other components
22
+
23
+ Returns
24
+ -------
25
+ None
26
+ """
27
+ self.id = f"tool_outputs_{id}"
28
+ self.create_viewmodel(id)
29
+ self.view_model.tool_outputs_bind.connect(self.id)
30
+ self.create_ui()
31
+
32
+ def create_viewmodel(self, id: str) -> None:
33
+ server = get_server(None, client_type="vue3")
34
+ binding = TrameBinding(server.state)
35
+ self.view_model = ToolOutputsViewModel(id, binding)
36
+
37
+ def create_ui(self) -> None:
38
+ with HBoxLayout(classes="d-flex", width="100%"):
39
+ with vuetify.VTabs(v_model=(f"{self.id}_active_output_tab", "1"), direction="vertical"):
40
+ vuetify.VTab("Console output", value=1)
41
+ vuetify.VTab("Console error", value=2)
42
+ with HBoxLayout(classes="flex-grow-1"):
43
+ InputField(
44
+ v_show=f"{self.id}_active_output_tab === '1'",
45
+ v_model=f"{self.id}.stdout",
46
+ id=f"{self.id}_outputs",
47
+ type="autoscroll",
48
+ auto_grow=True,
49
+ readonly=True,
50
+ max_rows="30",
51
+ )
52
+ InputField(
53
+ v_show=f"{self.id}_active_output_tab === '2'",
54
+ v_model=f"{self.id}.stderr",
55
+ id=f"{self.id}_errors",
56
+ type="autoscroll",
57
+ auto_grow=True,
58
+ readonly=True,
59
+ max_rows="30",
60
+ )
@@ -130,10 +130,14 @@ html {
130
130
  min-width: 0px !important;
131
131
  padding: 5px 5px !important;
132
132
  box-shadow: none !important;
133
- }
134
133
 
135
- .v-btn__content {
136
- text-transform: none;
134
+ .v-btn__content {
135
+ text-transform: none;
136
+ }
137
+
138
+ .v-btn__prepend {
139
+ margin-left: 1px;
140
+ }
137
141
  }
138
142
 
139
143
  .v-label {
@@ -145,6 +149,10 @@ html {
145
149
  padding: 5px;
146
150
  }
147
151
 
152
+ textarea.v-field__input {
153
+ mask-image: none !important;
154
+ }
155
+
148
156
  .v-field {
149
157
  margin: 8px 4px 8px 4px;
150
158
  }
@@ -0,0 +1,87 @@
1
+ """Module for the JobProgress ViewModel."""
2
+
3
+ import asyncio
4
+ from typing import Any, Optional
5
+
6
+ import blinker
7
+ from pydantic import BaseModel
8
+
9
+ from nova.common.job import WorkState
10
+ from nova.common.signals import Signal, ToolCommand, get_signal_id
11
+ from nova.mvvm.interface import BindingInterface
12
+
13
+
14
+ def job_running(status: WorkState) -> bool:
15
+ """A helper function to check if job is doing something in Galaxy."""
16
+ return status in [
17
+ WorkState.UPLOADING_DATA,
18
+ WorkState.QUEUED,
19
+ WorkState.RUNNING,
20
+ WorkState.STOPPING,
21
+ WorkState.CANCELING,
22
+ ]
23
+
24
+
25
+ class ButtonsState(BaseModel):
26
+ """Class that manages start/stop/cancel button states."""
27
+
28
+ run_disabled: bool = False
29
+ cancel_disabled: bool = True
30
+ stop_disabled: bool = True
31
+ download_disabled: bool = True
32
+
33
+ stop_in_progress: bool = False
34
+ cancel_in_progress: bool = False
35
+ download_in_progress: bool = False
36
+
37
+ def update_from_workstate(self, status: WorkState) -> None:
38
+ running = job_running(status)
39
+ self.run_disabled = running
40
+ self.cancel_disabled = not running
41
+ self.stop_disabled = status not in [WorkState.RUNNING, WorkState.STOPPING]
42
+ self.stop_in_progress = status == WorkState.STOPPING
43
+ self.cancel_in_progress = status == WorkState.CANCELING
44
+ self.download_disabled = status != WorkState.FINISHED
45
+
46
+
47
+ class ExecutionButtonsViewModel:
48
+ """A viewmodel responsible for execution buttons."""
49
+
50
+ def __init__(self, id: str, binding: BindingInterface):
51
+ self.sender_id = f"ExecutionButtonsViewModel_{id}"
52
+ self.button_states = ButtonsState()
53
+ self.buttons_state_bind = binding.new_bind(self.button_states)
54
+ self.execution_signal = blinker.signal(get_signal_id(id, Signal.TOOL_COMMAND))
55
+ self.progress_signal = blinker.signal(get_signal_id(id, Signal.PROGRESS))
56
+ self.progress_signal.connect(self.update_state, weak=False)
57
+
58
+ async def update_state(self, _sender: Any, state: WorkState, details: str) -> None:
59
+ self.button_states.update_from_workstate(state)
60
+ self.buttons_state_bind.update_in_view(self.button_states)
61
+
62
+ async def run(self) -> None:
63
+ # disable run now since it might take some time before the client updates the status
64
+ self.button_states.run_disabled = True
65
+ self.buttons_state_bind.update_in_view(self.button_states)
66
+ await self.execution_signal.send_async(self.sender_id, command=ToolCommand.START)
67
+
68
+ async def cancel(self) -> None:
69
+ await self.execution_signal.send_async(self.sender_id, command=ToolCommand.CANCEL)
70
+
71
+ async def stop(self) -> None:
72
+ await self.execution_signal.send_async(self.sender_id, command=ToolCommand.CANCEL)
73
+
74
+ async def prepare_results(self) -> Optional[bytes]:
75
+ self.button_states.download_in_progress = True
76
+ self.buttons_state_bind.update_in_view(self.button_states)
77
+ await asyncio.sleep(0.5) # to give Trame time to update view
78
+ responses = await self.execution_signal.send_async(self.sender_id, command=ToolCommand.GET_RESULTS)
79
+ res = None
80
+ for response in responses: # responses can come from multiple places
81
+ if response[1] is None:
82
+ continue
83
+ if response[1]["sender"] == self.sender_id and response[1]["command"] == ToolCommand.GET_RESULTS:
84
+ res = response[1]["results"]
85
+ self.button_states.download_in_progress = False
86
+ self.buttons_state_bind.update_in_view(self.button_states)
87
+ return res
@@ -0,0 +1,75 @@
1
+ """Module for the JobProgress ViewModel."""
2
+
3
+ from typing import Any
4
+
5
+ import blinker
6
+ from pydantic import BaseModel
7
+
8
+ from nova.common.job import WorkState
9
+ from nova.common.signals import Signal, get_signal_id
10
+ from nova.mvvm.interface import BindingInterface
11
+
12
+
13
+ def details_from_state(state: WorkState) -> str:
14
+ work_state_map = {
15
+ WorkState.NOT_STARTED: "job not started",
16
+ WorkState.UPLOADING_DATA: "uploading data",
17
+ WorkState.QUEUED: "job is queued",
18
+ WorkState.RUNNING: "job is running",
19
+ WorkState.FINISHED: "job finished",
20
+ WorkState.ERROR: "job produced an error",
21
+ WorkState.DELETED: "job deleted",
22
+ WorkState.CANCELED: "job canceled",
23
+ WorkState.STOPPING: "stopping job",
24
+ WorkState.CANCELING: "canceling job",
25
+ }
26
+ if state in work_state_map:
27
+ return work_state_map[state]
28
+ else:
29
+ return state.value
30
+
31
+
32
+ class ProgressState(BaseModel):
33
+ """Class that manages progress bars states."""
34
+
35
+ progress: str = ""
36
+ details: str = ""
37
+ show_progress: bool = False
38
+ show_failed: bool = False
39
+ show_ok: bool = False
40
+
41
+ def update_from_workstate(self, state: WorkState) -> None:
42
+ progress = "0"
43
+ match state:
44
+ case WorkState.UPLOADING_DATA:
45
+ progress = "10"
46
+ case WorkState.QUEUED:
47
+ progress = "20"
48
+ case WorkState.RUNNING:
49
+ progress = "50"
50
+
51
+ self.show_progress = state in [
52
+ WorkState.UPLOADING_DATA,
53
+ WorkState.QUEUED,
54
+ WorkState.RUNNING,
55
+ WorkState.CANCELING,
56
+ WorkState.STOPPING,
57
+ ]
58
+ self.show_failed = state == WorkState.ERROR
59
+ self.show_ok = state == WorkState.FINISHED
60
+ self.progress = progress
61
+ self.details = details_from_state(state)
62
+
63
+
64
+ class ProgressBarViewModel:
65
+ """A viewmodel responsible for progress bar."""
66
+
67
+ def __init__(self, id: str, binding: BindingInterface):
68
+ self.progress_state = ProgressState()
69
+ self.progress_state_bind = binding.new_bind(self.progress_state)
70
+ self.progress_signal = blinker.signal(get_signal_id(id, Signal.PROGRESS))
71
+ self.progress_signal.connect(self.update_state, weak=False)
72
+
73
+ async def update_state(self, _sender: Any, state: WorkState, details: str) -> None:
74
+ self.progress_state.update_from_workstate(state)
75
+ self.progress_state_bind.update_in_view(self.progress_state)
@@ -0,0 +1,23 @@
1
+ """Module for the Tool ouputs ViewModel."""
2
+
3
+ from typing import Any
4
+
5
+ import blinker
6
+
7
+ from nova.common.job import ToolOutputs
8
+ from nova.common.signals import Signal, get_signal_id
9
+ from nova.mvvm.interface import BindingInterface
10
+
11
+
12
+ class ToolOutputsViewModel:
13
+ """A viewmodel responsible for tool stdout and stderr."""
14
+
15
+ def __init__(self, id: str, binding: BindingInterface):
16
+ self.tool_outputs = ToolOutputs()
17
+ self.tool_outputs_bind = binding.new_bind(self.tool_outputs)
18
+ self.outputs_signal = blinker.signal(get_signal_id(id, Signal.OUTPUTS))
19
+ self.outputs_signal.connect(self.on_outputs_update, weak=False)
20
+
21
+ async def on_outputs_update(self, _sender: Any, outputs: ToolOutputs) -> None:
22
+ self.tool_outputs = outputs
23
+ self.tool_outputs_bind.update_in_view(self.tool_outputs)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nova-trame
3
- Version: 0.19.1
3
+ Version: 0.20.0
4
4
  Summary: A Python Package for injecting curated themes and custom components into Trame applications
5
5
  License: MIT
6
6
  Keywords: NDIP,Python,Trame,Vuetify
@@ -14,8 +14,10 @@ Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
16
  Requires-Dist: altair
17
+ Requires-Dist: blinker (>=1.9.0,<2.0.0)
17
18
  Requires-Dist: libsass
18
19
  Requires-Dist: mergedeep
20
+ Requires-Dist: nova-common (>=0.2.0)
19
21
  Requires-Dist: nova-mvvm
20
22
  Requires-Dist: pydantic
21
23
  Requires-Dist: tomli
@@ -4,9 +4,12 @@ nova/trame/model/data_selector.py,sha256=sCLU0YIMCccC1BH8dKZPmajaft6WgwuM_zOr3NP
4
4
  nova/trame/model/remote_file_input.py,sha256=9KAf31ZHzpsh_aXUrNcF81Q5jvUZDWCzW1QATKls-Jk,3675
5
5
  nova/trame/view/components/__init__.py,sha256=u8yzshFp_TmuC1g9TRxKjy_BdGWMIzPQouI52hzcr2U,234
6
6
  nova/trame/view/components/data_selector.py,sha256=UFQriSH25wk3F4s6EnP5Dfrt3MFbisJTrzmRxH0ME8U,8831
7
+ nova/trame/view/components/execution_buttons.py,sha256=wXpgF6osuAivKKnby8yCevoFa4PbRlCtUgEymjiugzE,3857
7
8
  nova/trame/view/components/file_upload.py,sha256=7VcpfA6zmiqMDLkwVPlb35Tf0IUTBN1xsHpoUFnSr1w,3111
8
- nova/trame/view/components/input_field.py,sha256=Wf-WuYm9SmbHXYy1cvYy8zDWe5rhvLcUHmwqmBvqVxk,16074
9
+ nova/trame/view/components/input_field.py,sha256=q6WQ_N-BOlimUL9zgazDlsDfK28FrrKjH4he8e_HzRA,16088
10
+ nova/trame/view/components/progress_bar.py,sha256=vva_blYZwF1xJt72sCBSbJB5HloyTy39UoVOP_ixs_E,1995
9
11
  nova/trame/view/components/remote_file_input.py,sha256=ByrBFj8svyWezcardCWrS_4Ag3fgTYNg_11lDW1FIA8,9669
12
+ nova/trame/view/components/tool_outputs.py,sha256=IlpvJJuVZ6o9WMXW1Hpsvca7Xw1AxO83fdr_JFcYnbA,2146
10
13
  nova/trame/view/components/visualization/__init__.py,sha256=reqkkbhD5uSksHHlhVMy1qNUCwSekS5HlXk6wCREYxU,152
11
14
  nova/trame/view/components/visualization/interactive_2d_plot.py,sha256=foZCMoqbuahT5dtqIQvm8C4ZJcY9P211eJEcpQJltmM,3421
12
15
  nova/trame/view/components/visualization/matplotlib_figure.py,sha256=0iWCXB8i7Tut1gA66hY9cGrhZPaHC7p-XdADDNy_UVY,12042
@@ -16,7 +19,7 @@ nova/trame/view/layouts/hbox.py,sha256=qlOMp_iOropIkC9Jxa6D89b7OPv0pNvJ73tUEzddy
16
19
  nova/trame/view/layouts/utils.py,sha256=Hg34VQWTG3yHBsgNvmfatR4J-uL3cko7UxSJpT-h3JI,376
17
20
  nova/trame/view/layouts/vbox.py,sha256=hzhzPu99R2fAclMe-FwHZseJWk7iailZ31bKdGhi1hk,3514
18
21
  nova/trame/view/theme/__init__.py,sha256=70_marDlTigIcPEOGiJb2JTs-8b2sGM5SlY7XBPtBDM,54
19
- nova/trame/view/theme/assets/core_style.scss,sha256=kxm66OdHeSAW1_zWJXWpzBZ30hopI1cgLwyXrqdmf1I,3214
22
+ nova/trame/view/theme/assets/core_style.scss,sha256=5i2cZQbmaQTpcNgsaVRdcubh0HHidqPnnLj8McHNkbY,3367
20
23
  nova/trame/view/theme/assets/favicon.png,sha256=Xbp1nUmhcBDeObjsebEbEAraPDZ_M163M_ZLtm5AbQc,1927
21
24
  nova/trame/view/theme/assets/js/delay_manager.js,sha256=vmb34DZ5YCQIlRW9Tf2M_uvJW6HFCmtlKZ5e_TPR8yg,536
22
25
  nova/trame/view/theme/assets/js/lodash.debounce.min.js,sha256=GLzlQH04WDUNYN7i39ttHHejSdu-CpAvfWgDgKDn-OY,4448
@@ -25,9 +28,12 @@ nova/trame/view/theme/assets/vuetify_config.json,sha256=a0FSgpLYWGFlRGSMhMq61MyD
25
28
  nova/trame/view/theme/theme.py,sha256=HUeuVfzEgeYW65W-LcvXzfYNRHu6aQibGwwgHGyh3OA,11765
26
29
  nova/trame/view/utilities/local_storage.py,sha256=vD8f2VZIpxhIKjZwEaD7siiPCTZO4cw9AfhwdawwYLY,3218
27
30
  nova/trame/view_model/data_selector.py,sha256=FM1Xe-f-gi1jVwA9nDf2KE1UDvsAvmMKlp78slIpX58,2418
31
+ nova/trame/view_model/execution_buttons.py,sha256=MfKSp95D92EqpD48C15cBo6dLO0Yld4FeRZMJNxJf7Y,3551
32
+ nova/trame/view_model/progress_bar.py,sha256=L7ED6TDn5v2142iu-qt3i-jUg_5JEhLyC476t2OtohU,2467
28
33
  nova/trame/view_model/remote_file_input.py,sha256=ojEOJ8ZPkajpbAaZi9VLj7g-uBjhb8BMrTdMmwf_J6A,3367
29
- nova_trame-0.19.1.dist-info/LICENSE,sha256=Iu5QiDbwNbREg75iYaxIJ_V-zppuv4QFuBhAW-qiAlM,1061
30
- nova_trame-0.19.1.dist-info/METADATA,sha256=KAU_st5MU5BO6fNu45_wh3J-DP8QwKastiWR2cCRpnA,1446
31
- nova_trame-0.19.1.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
32
- nova_trame-0.19.1.dist-info/entry_points.txt,sha256=J2AmeSwiTYZ4ZqHHp9HO6v4MaYQTTBPbNh6WtoqOT58,42
33
- nova_trame-0.19.1.dist-info/RECORD,,
34
+ nova/trame/view_model/tool_outputs.py,sha256=ev6LY7fJ0H2xAJn9f5ww28c8Kpom2SYc2FbvFcoN4zg,829
35
+ nova_trame-0.20.0.dist-info/LICENSE,sha256=Iu5QiDbwNbREg75iYaxIJ_V-zppuv4QFuBhAW-qiAlM,1061
36
+ nova_trame-0.20.0.dist-info/METADATA,sha256=0URjV5ll_dfW8d0kB_EnpCa0ttbNEObIsk0J8HevEu0,1523
37
+ nova_trame-0.20.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
38
+ nova_trame-0.20.0.dist-info/entry_points.txt,sha256=J2AmeSwiTYZ4ZqHHp9HO6v4MaYQTTBPbNh6WtoqOT58,42
39
+ nova_trame-0.20.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.2
2
+ Generator: poetry-core 2.1.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any