xconv2 0.0.0__tar.gz

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.
Files changed (99) hide show
  1. xconv2-0.0.0/LICENSE +21 -0
  2. xconv2-0.0.0/PKG-INFO +141 -0
  3. xconv2-0.0.0/README.md +112 -0
  4. xconv2-0.0.0/pyproject.toml +68 -0
  5. xconv2-0.0.0/setup.cfg +4 -0
  6. xconv2-0.0.0/tests/test_animation_protocol.py +247 -0
  7. xconv2-0.0.0/tests/test_animation_router.py +209 -0
  8. xconv2-0.0.0/tests/test_animation_session.py +223 -0
  9. xconv2-0.0.0/tests/test_cache_utils.py +84 -0
  10. xconv2-0.0.0/tests/test_cf_interface.py +1515 -0
  11. xconv2-0.0.0/tests/test_contour.py +535 -0
  12. xconv2-0.0.0/tests/test_contour_options_dialog.py +161 -0
  13. xconv2-0.0.0/tests/test_coordinate_flow.py +1961 -0
  14. xconv2-0.0.0/tests/test_field_metadata_controller.py +151 -0
  15. xconv2-0.0.0/tests/test_filter_options_dialog.py +67 -0
  16. xconv2-0.0.0/tests/test_gui_cli.py +61 -0
  17. xconv2-0.0.0/tests/test_https_integration.py +74 -0
  18. xconv2-0.0.0/tests/test_lineplot.py +407 -0
  19. xconv2-0.0.0/tests/test_load_selected_file.py +2519 -0
  20. xconv2-0.0.0/tests/test_log_viewer_dialog.py +132 -0
  21. xconv2-0.0.0/tests/test_pyfive_remote_s3_cache.py +157 -0
  22. xconv2-0.0.0/tests/test_remote_access.py +403 -0
  23. xconv2-0.0.0/tests/test_remote_auth_flow.py +306 -0
  24. xconv2-0.0.0/tests/test_remote_configuration_dialog.py +464 -0
  25. xconv2-0.0.0/tests/test_remote_file_navigator.py +339 -0
  26. xconv2-0.0.0/tests/test_remote_fs.py +548 -0
  27. xconv2-0.0.0/tests/test_science_environment.py +200 -0
  28. xconv2-0.0.0/tests/test_science_stack_dialog.py +131 -0
  29. xconv2-0.0.0/tests/test_selection_controller.py +108 -0
  30. xconv2-0.0.0/tests/test_service_helpers.py +137 -0
  31. xconv2-0.0.0/tests/test_settings_store.py +36 -0
  32. xconv2-0.0.0/tests/test_worker_message_router.py +223 -0
  33. xconv2-0.0.0/tests/test_worker_remote_integration.py +1358 -0
  34. xconv2-0.0.0/tests/test_worker_remote_tasks.py +618 -0
  35. xconv2-0.0.0/tests/test_worker_save_script.py +84 -0
  36. xconv2-0.0.0/tests/test_workflow_provenance.py +179 -0
  37. xconv2-0.0.0/xconv2/__init__.py +19 -0
  38. xconv2-0.0.0/xconv2/aaa/aaa_config.py +64 -0
  39. xconv2-0.0.0/xconv2/animation_protocol.py +189 -0
  40. xconv2-0.0.0/xconv2/animation_session.py +165 -0
  41. xconv2-0.0.0/xconv2/assets/Pyfive-logo.png +0 -0
  42. xconv2-0.0.0/xconv2/assets/UoR-logo.png +0 -0
  43. xconv2-0.0.0/xconv2/assets/cf-logo-t.svg +30367 -0
  44. xconv2-0.0.0/xconv2/assets/cf-logo.svg +30375 -0
  45. xconv2-0.0.0/xconv2/assets/clipster.svg +71 -0
  46. xconv2-0.0.0/xconv2/assets/dask-logo.svg +13 -0
  47. xconv2-0.0.0/xconv2/assets/info-ncas.svg +5 -0
  48. xconv2-0.0.0/xconv2/assets/ncas-logo.png +0 -0
  49. xconv2-0.0.0/xconv2/assets/tooltip.svg +1 -0
  50. xconv2-0.0.0/xconv2/assets/under-construction.svg +9 -0
  51. xconv2-0.0.0/xconv2/cache_utils.py +293 -0
  52. xconv2-0.0.0/xconv2/cell_method_handler.py +92 -0
  53. xconv2-0.0.0/xconv2/cf_interface/__init__.py +76 -0
  54. xconv2-0.0.0/xconv2/cf_interface/filtering.py +183 -0
  55. xconv2-0.0.0/xconv2/cf_interface/lineplot.py +262 -0
  56. xconv2-0.0.0/xconv2/cf_interface/maths.py +344 -0
  57. xconv2-0.0.0/xconv2/cf_interface/metadata_operations.py +663 -0
  58. xconv2-0.0.0/xconv2/cf_interface/plot_layout_helpers.py +139 -0
  59. xconv2-0.0.0/xconv2/cf_interface/plotting.py +533 -0
  60. xconv2-0.0.0/xconv2/cf_interface/regridding.py +189 -0
  61. xconv2-0.0.0/xconv2/cf_templates.py +511 -0
  62. xconv2-0.0.0/xconv2/colour_scales.py +82 -0
  63. xconv2-0.0.0/xconv2/coordinate_subspace.py +87 -0
  64. xconv2-0.0.0/xconv2/core_window.py +2651 -0
  65. xconv2-0.0.0/xconv2/gui.py +128 -0
  66. xconv2-0.0.0/xconv2/logging_utils.py +145 -0
  67. xconv2-0.0.0/xconv2/main_window.py +2232 -0
  68. xconv2-0.0.0/xconv2/main_window_components/__init__.py +1 -0
  69. xconv2-0.0.0/xconv2/main_window_components/plot_ops.py +327 -0
  70. xconv2-0.0.0/xconv2/main_window_components/remote_auth_ops.py +440 -0
  71. xconv2-0.0.0/xconv2/main_window_components/remote_flow_ops.py +746 -0
  72. xconv2-0.0.0/xconv2/main_window_components/replay_ops.py +474 -0
  73. xconv2-0.0.0/xconv2/remote_access.py +758 -0
  74. xconv2-0.0.0/xconv2/remote_fs.py +791 -0
  75. xconv2-0.0.0/xconv2/science_environment.py +115 -0
  76. xconv2-0.0.0/xconv2/tooltips.py +104 -0
  77. xconv2-0.0.0/xconv2/ui/__init__.py +19 -0
  78. xconv2-0.0.0/xconv2/ui/contour_options_controller.py +758 -0
  79. xconv2-0.0.0/xconv2/ui/dialogs.py +2991 -0
  80. xconv2-0.0.0/xconv2/ui/field_metadata_controller.py +378 -0
  81. xconv2-0.0.0/xconv2/ui/lineplot_options_controller.py +176 -0
  82. xconv2-0.0.0/xconv2/ui/menu_controller.py +224 -0
  83. xconv2-0.0.0/xconv2/ui/plot_options_shared.py +258 -0
  84. xconv2-0.0.0/xconv2/ui/plot_view_controller.py +831 -0
  85. xconv2-0.0.0/xconv2/ui/remote_file_navigator.py +1226 -0
  86. xconv2-0.0.0/xconv2/ui/science_stack_dialog.py +21 -0
  87. xconv2-0.0.0/xconv2/ui/selection_controller.py +623 -0
  88. xconv2-0.0.0/xconv2/ui/settings_store.py +297 -0
  89. xconv2-0.0.0/xconv2/ui/vector_options_controller.py +197 -0
  90. xconv2-0.0.0/xconv2/worker.py +1566 -0
  91. xconv2-0.0.0/xconv2/worker_message_router.py +478 -0
  92. xconv2-0.0.0/xconv2/workflow/provenance_export.py +416 -0
  93. xconv2-0.0.0/xconv2/workflow/xconv_workflow_to_prov.py +301 -0
  94. xconv2-0.0.0/xconv2.egg-info/PKG-INFO +141 -0
  95. xconv2-0.0.0/xconv2.egg-info/SOURCES.txt +97 -0
  96. xconv2-0.0.0/xconv2.egg-info/dependency_links.txt +1 -0
  97. xconv2-0.0.0/xconv2.egg-info/entry_points.txt +5 -0
  98. xconv2-0.0.0/xconv2.egg-info/requires.txt +21 -0
  99. xconv2-0.0.0/xconv2.egg-info/top_level.txt +1 -0
xconv2-0.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NCAS University of Reading
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
xconv2-0.0.0/PKG-INFO ADDED
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.4
2
+ Name: xconv2
3
+ Version: 0.0.0
4
+ Summary: High-performance data viewer and simple data converter
5
+ Requires-Python: >=3.12
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: cf-plot>=3.5.0
9
+ Requires-Dist: cf-python>=3.21.0
10
+ Requires-Dist: fsspec
11
+ Requires-Dist: numpy
12
+ Requires-Dist: pandas
13
+ Requires-Dist: paramiko
14
+ Requires-Dist: pillow
15
+ Requires-Dist: prov
16
+ Requires-Dist: pyfive>=1.2.0
17
+ Requires-Dist: PySide6
18
+ Requires-Dist: pystac-client
19
+ Requires-Dist: rdflib
20
+ Requires-Dist: s3fs
21
+ Requires-Dist: setuptools>80
22
+ Requires-Dist: superqt
23
+ Requires-Dist: umfive>=0.2.2
24
+ Requires-Dist: zarr
25
+ Provides-Extra: test
26
+ Requires-Dist: cryptography; extra == "test"
27
+ Requires-Dist: pytest; extra == "test"
28
+ Dynamic: license-file
29
+
30
+ # xconv2
31
+
32
+ xconv2 provides (or will provide) a graphical interface to local and remote weather and climate data. It is a replacement for the venerable [xconv](https://ncas-cms.github.io/xconv-doc/html/index.html) package that has been supported by NCAS CMS for decades.
33
+
34
+ <table>
35
+ <tr>
36
+ <td style="vertical-align: middle; padding: 2px;">
37
+ <img src="xconv2/assets/under-construction.svg" alt="Under construction icon" height="128px" />
38
+ </td>
39
+ <td style="vertical-align: middle;">
40
+ This version is under development. If you encounter something that looks like it should work, but doesn't, please
41
+ raise a bug issue at the address found in the About menu. If there is function that you would like to see, feel
42
+ free to raise an enhancement request at the same location.
43
+ </td>
44
+ </tr>
45
+ </table>
46
+
47
+ ## Installation
48
+
49
+ We intend to release this as a standalone executable for linux and macos, but for the moment you should use a Python 3.12 (or later) environment with
50
+ udunits2 and cartopy installed (from conda-forge), then you can pip install from the source using one of these:
51
+
52
+ ### Alpha
53
+
54
+ We intend to release this as a standalone application (an "app") eventually, but meanwhile, you will likely need a dedicated (or up-to-date) mamba environment with a Python 3.12 (or later) environment with `cartopy` and `udunits2` installed, then you can pip install from PyPI. Here's an example:
55
+
56
+ ```bash
57
+ conda create -n xconv2
58
+ conda activate xconv2
59
+ mamba install -c conda-forge pip cartopy udunits2
60
+ pip install xconv2
61
+ ```
62
+
63
+ and then you should have `xconv2` available on your command line in that environment. You could of course pip install into your own environment, but you will need to ensure it has cartopy and udunits2 in it (both via conda or mamba).
64
+
65
+ ### Conda Environments
66
+
67
+ - `environment.yml`: base development/runtime environment.
68
+ - `environment-integration.yml`: optional integration-test environment, including
69
+ pytest and cryptography for local HTTPS tests, and conda-forge SeaweedFS 4.48
70
+ (the native `weed` binary) for S3 integration tests. No Docker engine, CLI, or Python
71
+ Docker/MinIO packages are needed by the application or tests.
72
+
73
+ See [Running tests](docs/dev_notes.md#running-tests) for setup and skip conditions.
74
+
75
+ ### Optional SSH support
76
+
77
+ SSH browsing requires the optional `p5rem` package. It is not a dependency of
78
+ the PyPI package or the base Conda environment. Install it explicitly into the
79
+ environment used to run xconv2:
80
+
81
+ ```bash
82
+ pip install p5rem
83
+ ```
84
+
85
+ Without `p5rem`, the SSH tab is greyed out in the remote browsing and
86
+ configuration dialogs. Clicking it displays installation guidance; S3 and
87
+ HTTPS remain available.
88
+
89
+ ## Documentation
90
+
91
+ No user documentation is yet available, but it is designed to be intuitive
92
+ and basic usage should be possible without documentation once it is installed.
93
+
94
+ ### Raising issues
95
+
96
+ Please raise any issues using the github link from the
97
+ help menu. In doing so, it would be helpful to report
98
+ the science stack used from the science stack report
99
+ also available from the help menu.
100
+
101
+
102
+
103
+ ### Developer Documentation
104
+
105
+ Limited developer documentation is available. For the moment it is
106
+ limited to the following:
107
+
108
+ #### UML Diagrams
109
+
110
+ Rendered diagrams (GitHub-friendly):
111
+
112
+ - `docs/uml/svg/CoreWindowCurrentClass.svg`
113
+ - `docs/uml/svg/MainWindowComponentMap.svg`
114
+ - `docs/uml/svg/CoreWindowGuiWorkerSignals.svg`
115
+ - `docs/uml/svg/CoreWindowPhase3OptionsSequence.svg`
116
+ - `docs/uml/svg/RemoteWorkerWarmupSequence.svg`
117
+ - `docs/uml/svg/RemoteAccessModuleCurrent.svg`
118
+
119
+ PlantUML sources:
120
+
121
+ - `docs/uml/alpha_core_window.pu`
122
+ - `docs/uml/main_window_component_map.pu`
123
+ - `docs/uml/core_window_gui_worker_signals.puml`
124
+ - `docs/uml/core_window_options_sequence.puml`
125
+ - `docs/uml/remote_worker_warmup_sequence.puml`
126
+ - `docs/uml/remote_access_module.puml`
127
+
128
+ Regenerate SVGs after editing source diagrams:
129
+
130
+ ```bash
131
+ ./tools/generate_uml_svgs.sh
132
+ ```
133
+
134
+ #### Architecture Notes
135
+
136
+ - `docs/architecture/core_window_refactor.md`
137
+ - `docs/architecture/remote_navigation_and_worker_warmup.md`
138
+
139
+ ## License
140
+
141
+ This project is licensed under the MIT License. See `LICENSE`.
xconv2-0.0.0/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # xconv2
2
+
3
+ xconv2 provides (or will provide) a graphical interface to local and remote weather and climate data. It is a replacement for the venerable [xconv](https://ncas-cms.github.io/xconv-doc/html/index.html) package that has been supported by NCAS CMS for decades.
4
+
5
+ <table>
6
+ <tr>
7
+ <td style="vertical-align: middle; padding: 2px;">
8
+ <img src="xconv2/assets/under-construction.svg" alt="Under construction icon" height="128px" />
9
+ </td>
10
+ <td style="vertical-align: middle;">
11
+ This version is under development. If you encounter something that looks like it should work, but doesn't, please
12
+ raise a bug issue at the address found in the About menu. If there is function that you would like to see, feel
13
+ free to raise an enhancement request at the same location.
14
+ </td>
15
+ </tr>
16
+ </table>
17
+
18
+ ## Installation
19
+
20
+ We intend to release this as a standalone executable for linux and macos, but for the moment you should use a Python 3.12 (or later) environment with
21
+ udunits2 and cartopy installed (from conda-forge), then you can pip install from the source using one of these:
22
+
23
+ ### Alpha
24
+
25
+ We intend to release this as a standalone application (an "app") eventually, but meanwhile, you will likely need a dedicated (or up-to-date) mamba environment with a Python 3.12 (or later) environment with `cartopy` and `udunits2` installed, then you can pip install from PyPI. Here's an example:
26
+
27
+ ```bash
28
+ conda create -n xconv2
29
+ conda activate xconv2
30
+ mamba install -c conda-forge pip cartopy udunits2
31
+ pip install xconv2
32
+ ```
33
+
34
+ and then you should have `xconv2` available on your command line in that environment. You could of course pip install into your own environment, but you will need to ensure it has cartopy and udunits2 in it (both via conda or mamba).
35
+
36
+ ### Conda Environments
37
+
38
+ - `environment.yml`: base development/runtime environment.
39
+ - `environment-integration.yml`: optional integration-test environment, including
40
+ pytest and cryptography for local HTTPS tests, and conda-forge SeaweedFS 4.48
41
+ (the native `weed` binary) for S3 integration tests. No Docker engine, CLI, or Python
42
+ Docker/MinIO packages are needed by the application or tests.
43
+
44
+ See [Running tests](docs/dev_notes.md#running-tests) for setup and skip conditions.
45
+
46
+ ### Optional SSH support
47
+
48
+ SSH browsing requires the optional `p5rem` package. It is not a dependency of
49
+ the PyPI package or the base Conda environment. Install it explicitly into the
50
+ environment used to run xconv2:
51
+
52
+ ```bash
53
+ pip install p5rem
54
+ ```
55
+
56
+ Without `p5rem`, the SSH tab is greyed out in the remote browsing and
57
+ configuration dialogs. Clicking it displays installation guidance; S3 and
58
+ HTTPS remain available.
59
+
60
+ ## Documentation
61
+
62
+ No user documentation is yet available, but it is designed to be intuitive
63
+ and basic usage should be possible without documentation once it is installed.
64
+
65
+ ### Raising issues
66
+
67
+ Please raise any issues using the github link from the
68
+ help menu. In doing so, it would be helpful to report
69
+ the science stack used from the science stack report
70
+ also available from the help menu.
71
+
72
+
73
+
74
+ ### Developer Documentation
75
+
76
+ Limited developer documentation is available. For the moment it is
77
+ limited to the following:
78
+
79
+ #### UML Diagrams
80
+
81
+ Rendered diagrams (GitHub-friendly):
82
+
83
+ - `docs/uml/svg/CoreWindowCurrentClass.svg`
84
+ - `docs/uml/svg/MainWindowComponentMap.svg`
85
+ - `docs/uml/svg/CoreWindowGuiWorkerSignals.svg`
86
+ - `docs/uml/svg/CoreWindowPhase3OptionsSequence.svg`
87
+ - `docs/uml/svg/RemoteWorkerWarmupSequence.svg`
88
+ - `docs/uml/svg/RemoteAccessModuleCurrent.svg`
89
+
90
+ PlantUML sources:
91
+
92
+ - `docs/uml/alpha_core_window.pu`
93
+ - `docs/uml/main_window_component_map.pu`
94
+ - `docs/uml/core_window_gui_worker_signals.puml`
95
+ - `docs/uml/core_window_options_sequence.puml`
96
+ - `docs/uml/remote_worker_warmup_sequence.puml`
97
+ - `docs/uml/remote_access_module.puml`
98
+
99
+ Regenerate SVGs after editing source diagrams:
100
+
101
+ ```bash
102
+ ./tools/generate_uml_svgs.sh
103
+ ```
104
+
105
+ #### Architecture Notes
106
+
107
+ - `docs/architecture/core_window_refactor.md`
108
+ - `docs/architecture/remote_navigation_and_worker_warmup.md`
109
+
110
+ ## License
111
+
112
+ This project is licensed under the MIT License. See `LICENSE`.
@@ -0,0 +1,68 @@
1
+ [build-system]
2
+ requires = ["setuptools>=80"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "xconv2"
7
+ description = "High-performance data viewer and simple data converter"
8
+ requires-python = ">=3.12"
9
+ dependencies = [
10
+ "cf-plot>=3.5.0",
11
+ "cf-python>=3.21.0",
12
+ "fsspec",
13
+ "numpy",
14
+ "pandas",
15
+ "paramiko",
16
+ "pillow",
17
+ "prov",
18
+ "pyfive>=1.2.0",
19
+ "PySide6",
20
+ "pystac-client",
21
+ "rdflib",
22
+ "s3fs",
23
+ "setuptools>80", # cf-plot imports distutils
24
+ "superqt",
25
+ "umfive>=0.2.2",
26
+ "zarr",
27
+ ]
28
+ dynamic = [
29
+ "readme",
30
+ "version",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ test = [
35
+ "cryptography",
36
+ "pytest",
37
+ ]
38
+
39
+ [project.gui-scripts]
40
+ # This creates an 'xconv2' command that launches your GUI
41
+ xconv2 = "xconv2.gui:main"
42
+
43
+ [project.scripts]
44
+ # This creates a 'cf-worker' command for the backend process
45
+ cf-worker = "xconv2.worker:main"
46
+
47
+ [tool.setuptools.packages.find]
48
+ where = ["."]
49
+ include = ["xconv2*"]
50
+
51
+ [tool.setuptools.package-data]
52
+ xconv2 = ["assets/*.svg", "assets/*.png"]
53
+
54
+ [tool.setuptools.dynamic]
55
+ readme = {file = "README.md", content-type = "text/markdown"}
56
+
57
+ [tool.setuptools_scm]
58
+ version_scheme = "release-branch-semver"
59
+
60
+ [tool.pytest.ini_options]
61
+ testpaths = ["tests"]
62
+ markers = [
63
+ "integration: tests exercising local HTTPS or native SeaweedFS S3 services (no Docker)",
64
+ ]
65
+ filterwarnings = [
66
+ "ignore:distutils Version classes are deprecated. Use packaging.version instead.:DeprecationWarning:cfplot.cfplot",
67
+ "ignore:'maxsplit' is passed as positional argument:DeprecationWarning:cf.functions",
68
+ ]
xconv2-0.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,247 @@
1
+ """Tests for animation protocol encoding and decoding."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import pickle
7
+ import time
8
+
9
+ import pytest
10
+
11
+ from xconv2.animation_protocol import (
12
+ AnimationEnd,
13
+ AnimationError,
14
+ AnimationFrame,
15
+ AnimationMetadata,
16
+ AnimationStart,
17
+ decode_payload,
18
+ encode_payload,
19
+ )
20
+
21
+
22
+ class TestAnimationMetadata:
23
+ """Tests for AnimationMetadata serialization."""
24
+
25
+ def test_metadata_to_dict(self) -> None:
26
+ meta = AnimationMetadata(
27
+ session_id="sess-123",
28
+ total_frames=10,
29
+ fps_hint=12.0,
30
+ title_template="{title} [{frame}]",
31
+ plot_kind="contour",
32
+ levels_locked=True,
33
+ )
34
+
35
+ data = meta.to_dict()
36
+
37
+ assert data["session_id"] == "sess-123"
38
+ assert data["total_frames"] == 10
39
+ assert data["fps_hint"] == 12.0
40
+ assert data["title_template"] == "{title} [{frame}]"
41
+ assert data["plot_kind"] == "contour"
42
+ assert data["levels_locked"] is True
43
+
44
+ def test_metadata_from_dict(self) -> None:
45
+ data = {
46
+ "session_id": "sess-123",
47
+ "total_frames": 10,
48
+ "fps_hint": 12.0,
49
+ "title_template": "{title} [{frame}]",
50
+ "plot_kind": "contour",
51
+ "levels_locked": True,
52
+ }
53
+
54
+ meta = AnimationMetadata.from_dict(data)
55
+
56
+ assert meta.session_id == "sess-123"
57
+ assert meta.total_frames == 10
58
+ assert meta.fps_hint == 12.0
59
+ assert meta.title_template == "{title} [{frame}]"
60
+ assert meta.plot_kind == "contour"
61
+ assert meta.levels_locked is True
62
+
63
+ def test_metadata_from_dict_partial(self) -> None:
64
+ data = {"session_id": "sess-123"}
65
+
66
+ meta = AnimationMetadata.from_dict(data)
67
+
68
+ assert meta.session_id == "sess-123"
69
+ assert meta.total_frames is None
70
+ assert meta.fps_hint is None
71
+ assert meta.plot_kind == "contour"
72
+ assert meta.levels_locked is False
73
+
74
+
75
+ class TestAnimationFrame:
76
+ """Tests for AnimationFrame serialization."""
77
+
78
+ def test_frame_to_dict(self) -> None:
79
+ now = time.time()
80
+ png_data = b"PNG\x89\x01\x02\x03"
81
+
82
+ frame = AnimationFrame(
83
+ request_id="req-123",
84
+ session_id="sess-123",
85
+ frame_index=5,
86
+ total_frames=10,
87
+ png_bytes=png_data,
88
+ frame_value_label="frame 5",
89
+ emitted_at=now,
90
+ )
91
+
92
+ data = frame.to_dict()
93
+
94
+ assert data["request_id"] == "req-123"
95
+ assert data["session_id"] == "sess-123"
96
+ assert data["frame_index"] == 5
97
+ assert data["total_frames"] == 10
98
+ assert data["png_bytes"] == png_data
99
+ assert data["frame_value_label"] == "frame 5"
100
+ assert data["emitted_at"] == now
101
+
102
+ def test_frame_from_dict(self) -> None:
103
+ now = time.time()
104
+ png_data = b"PNG\x89\x01\x02\x03"
105
+
106
+ data = {
107
+ "request_id": "req-123",
108
+ "session_id": "sess-123",
109
+ "frame_index": 5,
110
+ "total_frames": 10,
111
+ "png_bytes": png_data,
112
+ "frame_value_label": "frame 5",
113
+ "emitted_at": now,
114
+ }
115
+
116
+ frame = AnimationFrame.from_dict(data)
117
+
118
+ assert frame.request_id == "req-123"
119
+ assert frame.session_id == "sess-123"
120
+ assert frame.frame_index == 5
121
+ assert frame.total_frames == 10
122
+ assert frame.png_bytes == png_data
123
+ assert frame.frame_value_label == "frame 5"
124
+ assert frame.emitted_at == now
125
+
126
+
127
+ class TestAnimationStart:
128
+ """Tests for AnimationStart serialization."""
129
+
130
+ def test_start_to_dict(self) -> None:
131
+ now = time.time()
132
+
133
+ start = AnimationStart(
134
+ request_id="req-123",
135
+ session_id="sess-123",
136
+ total_frames=10,
137
+ fps_hint=12.0,
138
+ title_template="{title} [{frame}]",
139
+ started_at=now,
140
+ )
141
+
142
+ data = start.to_dict()
143
+
144
+ assert data["request_id"] == "req-123"
145
+ assert data["session_id"] == "sess-123"
146
+ assert data["total_frames"] == 10
147
+ assert data["fps_hint"] == 12.0
148
+ assert data["title_template"] == "{title} [{frame}]"
149
+ assert data["started_at"] == now
150
+
151
+ def test_start_from_dict(self) -> None:
152
+ now = time.time()
153
+
154
+ data = {
155
+ "request_id": "req-123",
156
+ "session_id": "sess-123",
157
+ "total_frames": 10,
158
+ "fps_hint": 12.0,
159
+ "title_template": "{title} [{frame}]",
160
+ "started_at": now,
161
+ }
162
+
163
+ start = AnimationStart.from_dict(data)
164
+
165
+ assert start.request_id == "req-123"
166
+ assert start.session_id == "sess-123"
167
+ assert start.total_frames == 10
168
+ assert start.fps_hint == 12.0
169
+ assert start.title_template == "{title} [{frame}]"
170
+ assert start.started_at == now
171
+
172
+
173
+ class TestAnimationEnd:
174
+ """Tests for AnimationEnd serialization."""
175
+
176
+ def test_end_to_dict(self) -> None:
177
+ now = time.time()
178
+
179
+ end = AnimationEnd(
180
+ request_id="req-123",
181
+ session_id="sess-123",
182
+ frames_emitted=10,
183
+ completed_at=now,
184
+ )
185
+
186
+ data = end.to_dict()
187
+
188
+ assert data["request_id"] == "req-123"
189
+ assert data["session_id"] == "sess-123"
190
+ assert data["frames_emitted"] == 10
191
+ assert data["completed_at"] == now
192
+
193
+
194
+ class TestAnimationError:
195
+ """Tests for AnimationError serialization."""
196
+
197
+ def test_error_to_dict(self) -> None:
198
+ now = time.time()
199
+
200
+ error = AnimationError(
201
+ request_id="req-123",
202
+ session_id="sess-123",
203
+ frame_index=5,
204
+ error="Contour failed",
205
+ failed_at=now,
206
+ )
207
+
208
+ data = error.to_dict()
209
+
210
+ assert data["request_id"] == "req-123"
211
+ assert data["session_id"] == "sess-123"
212
+ assert data["frame_index"] == 5
213
+ assert data["error"] == "Contour failed"
214
+ assert data["failed_at"] == now
215
+
216
+
217
+ class TestPayloadEncoding:
218
+ """Tests for payload encoding and decoding."""
219
+
220
+ def test_encode_decode_roundtrip(self) -> None:
221
+ payload = {
222
+ "request_id": "req-123",
223
+ "session_id": "sess-123",
224
+ "frame_index": 5,
225
+ "data": "test",
226
+ }
227
+
228
+ encoded = encode_payload(payload)
229
+
230
+ assert isinstance(encoded, str)
231
+ assert encoded == base64.b64encode(pickle.dumps(payload)).decode("ascii")
232
+
233
+ decoded = decode_payload(encoded)
234
+
235
+ assert decoded == payload
236
+
237
+ def test_encode_with_binary_data(self) -> None:
238
+ payload = {
239
+ "png_bytes": b"PNG\x89\x01\x02\x03",
240
+ "request_id": "req-123",
241
+ }
242
+
243
+ encoded = encode_payload(payload)
244
+ decoded = decode_payload(encoded)
245
+
246
+ assert decoded["png_bytes"] == b"PNG\x89\x01\x02\x03"
247
+ assert decoded["request_id"] == "req-123"