ltpda 0.2.2__tar.gz → 0.2.3__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 (71) hide show
  1. {ltpda-0.2.2 → ltpda-0.2.3}/PKG-INFO +101 -1
  2. {ltpda-0.2.2 → ltpda-0.2.3}/README.md +100 -0
  3. ltpda-0.2.3/ltpda/Examples/ltpda_iplot.ipynb +828 -0
  4. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/dsp/__init__.py +17 -17
  5. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/dsp/noisegen.py +7 -0
  6. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/dsp/spectral.py +54 -1
  7. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/exceptions/__init__.py +18 -18
  8. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/exceptions/rangeexceptions.py +67 -67
  9. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/exceptions/sizeexceptions.py +31 -31
  10. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/exceptions/typeexceptions.py +49 -49
  11. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/fsdata.py +5 -1
  12. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/functions.py +11 -11
  13. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/history.py +67 -2
  14. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/mixins/__init__.py +13 -13
  15. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/mixins/_xydata_diff.py +229 -229
  16. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/mixins/_xydata_dsp.py +148 -148
  17. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/mixins/_xydata_operators.py +1 -0
  18. ltpda-0.2.3/ltpda/mixins/_xydata_plotter.py +1058 -0
  19. ltpda-0.2.3/ltpda/plotinfo.py +142 -0
  20. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/repo/_connection.py +96 -96
  21. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/repo/_xml_gen.py +172 -20
  22. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/repo/_xml_parse.py +90 -10
  23. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/repo/models.py +60 -60
  24. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/tsdata.py +69 -5
  25. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/utils/__init__.py +13 -13
  26. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/utils/_ltpda_obj.py +56 -1
  27. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/utils/math/__init__.py +16 -16
  28. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/utils/math/math.py +40 -40
  29. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/utils/prog/__init__.py +16 -16
  30. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/utils/prog/datatypes.py +27 -27
  31. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/xydata.py +22 -1
  32. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/ydata.py +4 -1
  33. {ltpda-0.2.2 → ltpda-0.2.3}/pyproject.toml +60 -59
  34. ltpda-0.2.2/ltpda/mixins/_xydata_plotter.py +0 -365
  35. {ltpda-0.2.2 → ltpda-0.2.3}/LICENSE.md +0 -0
  36. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_FIR_filter_testing.ipynb +0 -0
  37. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_IIR_filter_testing.ipynb +0 -0
  38. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_diff_testing.ipynb +0 -0
  39. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_example_delay.ipynb +0 -0
  40. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_function_constructors.ipynb +0 -0
  41. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_interpolation.ipynb +0 -0
  42. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_logpsd_test.ipynb +0 -0
  43. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_lpsd_testing.ipynb +0 -0
  44. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_ltpda_psd_compare.ipynb +0 -0
  45. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_psd_testing.ipynb +0 -0
  46. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_pzmodel.ipynb +0 -0
  47. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_repository.ipynb +0 -0
  48. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_repository_timeseries.ipynb +0 -0
  49. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_resample.ipynb +0 -0
  50. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_split_testing.ipynb +0 -0
  51. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_test_errorbars.ipynb +0 -0
  52. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_test_save_load.ipynb +0 -0
  53. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_testing.ipynb +0 -0
  54. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_tfe_testing.ipynb +0 -0
  55. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_tfestimate.ipynb +0 -0
  56. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_xydata_testing.ipynb +0 -0
  57. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/ltpda_ydata_testing.ipynb +0 -0
  58. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/Examples/test_lpsd_Laurant_timeseries.ipynb +0 -0
  59. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/__init__.py +0 -0
  60. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/dsp/filter.py +0 -0
  61. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/mixins/_tsdata_dsp.py +0 -0
  62. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/mixins/_ydata_operators.py +0 -0
  63. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/pzmodel.py +0 -0
  64. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/repo/__init__.py +0 -0
  65. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/repo/_retrieve.py +0 -0
  66. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/repo/_search.py +0 -0
  67. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/repo/_submit.py +0 -0
  68. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/repo/client.py +0 -0
  69. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/utils/axis.py +0 -0
  70. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/utils/specwin.py +0 -0
  71. {ltpda-0.2.2 → ltpda-0.2.3}/ltpda/utils/unit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ltpda
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Python fork of pyda (Hewitson et al.) — LTPDA-style signal processing with repository integration.
5
5
  License-File: LICENSE.md
6
6
  Author: Simon Barke
@@ -691,6 +691,38 @@ band = Sxx.split_by_frequency(freqs=[0.1, 1.0])
691
691
 
692
692
  ---
693
693
 
694
+ ### plotinfo — per-object style metadata
695
+
696
+ `set_plotinfo()` attaches persistent style and legend metadata to any ltpda object.
697
+ `iplot()` reads it automatically, with explicit `iplot()` kwargs taking priority.
698
+
699
+ ```python
700
+ ts.set_plotinfo(color='steelblue', linewidth=2.5, linestyle='--')
701
+ ts.iplot() # steelblue dashed line, width 2.5 — no extra kwargs
702
+
703
+ # Legend and error-bar control per object
704
+ sig.set_plotinfo(include_in_legend=False) # plotted but unlabelled
705
+ calib.set_plotinfo(show_errors=True) # error bars without ShowErrors=True kwarg
706
+
707
+ # Marker style
708
+ ts.set_plotinfo(marker='o', markersize=6, markerfacecolor='white', markeredgecolor='steelblue')
709
+ ```
710
+
711
+ Full parameter list: `color`, `linestyle`, `linewidth`, `marker`, `markersize`,
712
+ `markerfacecolor`, `markeredgecolor`, `fillmarkers`, `include_in_legend`, `show_errors`.
713
+
714
+ **Priority chain for each style field** — `iplot()` kwarg → `plotinfo` field → object
715
+ loose attribute (`obj.color`, `obj.linestyle`, …) → matplotlib default colour cycle.
716
+
717
+ **Repository interoperability** — plotinfo survives XML round-trips between Python and MATLAB:
718
+ - Python retrieve of a MATLAB AO: `<Style>` XML is parsed; `obj._plotinfo.color` is set to a
719
+ matplotlib hex string (`'#ff0000'`), all other fields populated from the XML attributes.
720
+ - Python submit: `<Style>` is generated with the exact Java color encoding MATLAB expects
721
+ (`Color.getRGB()` signed 32-bit decimal).
722
+ MATLAB retrieves a fully styled AO with the correct color, linestyle, marker, etc.
723
+
724
+ ---
725
+
694
726
  ### File I/O
695
727
 
696
728
  Objects are serialised to HDF5 with a versioned format. The file extension is `.ltpda`.
@@ -957,6 +989,74 @@ The following open issues are tracked upstream at
957
989
 
958
990
  ## Version history
959
991
 
992
+ ### 0.2.3
993
+
994
+ - `iplot()` — intelligent plot method mimicking MATLAB's `ao.iplot`:
995
+ - Smart data-type dispatch: `TSData` → linear axes; `FSData` → log-log with automatic
996
+ magnitude/phase subplots for complex data.
997
+ - `Arrangement='stacked'` (default) overlays all objects on the same axes.
998
+ - `Arrangement='subplots'` stacks each object in its own subplot row (single figure).
999
+ - `Arrangement='single'` opens one figure per object.
1000
+ - `XScales` / `YScales` — per-axis scale override (`'log'` or `'lin'`);
1001
+ a single string applies to all axes.
1002
+ - `XRanges` / `YRanges` — per-axis `[min, max]` limits.
1003
+ - `LineColors`, `LineStyles`, `LineWidths`, `Markers`, `MarkerSizes` — per-object
1004
+ style control; shorter lists cycle; `['all', value]` applies one value to every trace.
1005
+ - `MarkerFaceColor`, `MarkerEdgeColor` — independent marker fill and border colours;
1006
+ same `['all', colour]` shorthand supported.
1007
+ - `Legends='off'` suppresses legends; `Legends=['a', 'b']` overrides labels;
1008
+ `LegendLocation` accepts MATLAB location strings (`'NorthEast'`, `'Best'`, …);
1009
+ `LegendFontSize` controls font size; `ShowDescriptions=True` appends the object's
1010
+ `.description` attribute to the legend label.
1011
+ - `Titles` — per-subplot title strings (one per object in subplots/single arrangements).
1012
+ - `XLabels` / `YLabels` — override axis label names; data units are still appended.
1013
+ - `FigureNames` — set the figure suptitle / window title.
1014
+ - `complexPlotType` — controls complex-data display: `'absdeg'` (magnitude + phase in °,
1015
+ default), `'absrad'` (magnitude + phase in rad), `'realimag'` (real + imaginary parts).
1016
+ - `ShowErrors=True` renders error bars from `ddata`; `ErrorBarType='bar'` (default)
1017
+ or `'area'` (shaded band). Explicit per-object bounds via `YerrL`, `YerrU`,
1018
+ `XerrL`, `XerrU`. `AUTOERRORS=False` disables automatic `ddata` detection.
1019
+ - All keyword names match MATLAB's `iplot` exactly for zero relearning cost.
1020
+ - `plotinfo` — per-object style metadata that `iplot()` reads automatically.
1021
+ `set_plotinfo(color, linestyle, linewidth, marker, markersize, markerfacecolor,
1022
+ markeredgecolor, fillmarkers, include_in_legend, show_errors)` attaches a `PlotInfo`
1023
+ to any ltpda object. Priority chain: `iplot()` kwarg > plotinfo field > object loose
1024
+ attribute > matplotlib default. Full MATLAB XML round-trip: Python reads MATLAB
1025
+ `<Style>` XML on retrieve (all color, linestyle, marker fields parsed into matplotlib
1026
+ equivalents); Python emits exact MATLAB-compatible `<Style>` on submit (Java
1027
+ `Color.getRGB()` decimal encoding).
1028
+ - Richer Python AO processing history — Python history nodes are now as informative
1029
+ as MATLAB's and produce distinct per-operation groups in the MATLAB history browser:
1030
+ - Each operation type gets its own blue cluster label instead of the generic
1031
+ `Python/ltpda` bucket: `ao.ao (Python)` for constructors, `ao.psd (Python)` for
1032
+ spectral estimates, `ao.plus (Python)` for arithmetic, etc.
1033
+ - Constructor params are fully recorded: `FS`, `NSECS`, `YUNITS`, `WAVEFORM` (for
1034
+ `randn` / `sinewave`), `A0`, `F0`, `PHI` (for `sinewave`), `DISTRIBUTION` / `SIGMA`
1035
+ (for `randn`).
1036
+ - DSP functions (`psd`, `logpsd`, `mscohere`, `cohere`, `cpsd`, `tfe`) now record a
1037
+ history node that chains back to the input time-series, capturing `WINDOW`, `NAVS`,
1038
+ `PERCENT_OVERLAP`, `NFFT`, `SCALE`, `DETREND_ORDER` (and `PSLL`, `OLAP`, `BMIN`,
1039
+ `LMIN`, `JDES`, `KDES` for `logpsd`). Previously these functions produced no history
1040
+ at all.
1041
+ - `NoiseGen.generateNoise()` records `NSECS`, `FS`, `MODEL`, `YUNITS`.
1042
+ - `__pow__` records `EXPONENT`.
1043
+ - `set_description(text)` — explicit setter on all ltpda objects (mirrors MATLAB's
1044
+ `setDescription`). The `description` property remains directly assignable; this method
1045
+ adds a consistent `set_*` style for use alongside `set_yaxis_name`, `set_plotinfo`, etc.
1046
+ - Bug fixes:
1047
+ - History `context` attribute was silently dropped when Python read a MATLAB-serialized history
1048
+ node from XML and re-submitted it. MATLAB's history browser uses `context` to render "blue tag"
1049
+ cluster labels; losing it caused all pre-existing history steps to appear untagged after a
1050
+ Python round-trip. Fixed by adding a `_context` field to `HistoryNode` and preserving the
1051
+ attribute through the full read → write cycle.
1052
+ - `proctime` on history nodes drifted by the system UTC offset on every Python round-trip.
1053
+ `_parse_history_root` was creating naive datetimes via `datetime.utcfromtimestamp()`, which
1054
+ `datetime.timestamp()` (in the serialiser) then treated as local time. Switched to
1055
+ UTC-aware datetimes (`datetime.fromtimestamp(..., tz=timezone.utc)`) throughout.
1056
+ - AO `UUID` was not preserved on retrieve: `_parse_ao` discarded the `UUID` attribute from
1057
+ the `<ao>` element, so every re-submit generated a fresh random UUID. Now stamped onto
1058
+ `obj.id` after parsing.
1059
+
960
1060
  ### 0.2.2
961
1061
 
962
1062
  - First pypi.org release
@@ -665,6 +665,38 @@ band = Sxx.split_by_frequency(freqs=[0.1, 1.0])
665
665
 
666
666
  ---
667
667
 
668
+ ### plotinfo — per-object style metadata
669
+
670
+ `set_plotinfo()` attaches persistent style and legend metadata to any ltpda object.
671
+ `iplot()` reads it automatically, with explicit `iplot()` kwargs taking priority.
672
+
673
+ ```python
674
+ ts.set_plotinfo(color='steelblue', linewidth=2.5, linestyle='--')
675
+ ts.iplot() # steelblue dashed line, width 2.5 — no extra kwargs
676
+
677
+ # Legend and error-bar control per object
678
+ sig.set_plotinfo(include_in_legend=False) # plotted but unlabelled
679
+ calib.set_plotinfo(show_errors=True) # error bars without ShowErrors=True kwarg
680
+
681
+ # Marker style
682
+ ts.set_plotinfo(marker='o', markersize=6, markerfacecolor='white', markeredgecolor='steelblue')
683
+ ```
684
+
685
+ Full parameter list: `color`, `linestyle`, `linewidth`, `marker`, `markersize`,
686
+ `markerfacecolor`, `markeredgecolor`, `fillmarkers`, `include_in_legend`, `show_errors`.
687
+
688
+ **Priority chain for each style field** — `iplot()` kwarg → `plotinfo` field → object
689
+ loose attribute (`obj.color`, `obj.linestyle`, …) → matplotlib default colour cycle.
690
+
691
+ **Repository interoperability** — plotinfo survives XML round-trips between Python and MATLAB:
692
+ - Python retrieve of a MATLAB AO: `<Style>` XML is parsed; `obj._plotinfo.color` is set to a
693
+ matplotlib hex string (`'#ff0000'`), all other fields populated from the XML attributes.
694
+ - Python submit: `<Style>` is generated with the exact Java color encoding MATLAB expects
695
+ (`Color.getRGB()` signed 32-bit decimal).
696
+ MATLAB retrieves a fully styled AO with the correct color, linestyle, marker, etc.
697
+
698
+ ---
699
+
668
700
  ### File I/O
669
701
 
670
702
  Objects are serialised to HDF5 with a versioned format. The file extension is `.ltpda`.
@@ -931,6 +963,74 @@ The following open issues are tracked upstream at
931
963
 
932
964
  ## Version history
933
965
 
966
+ ### 0.2.3
967
+
968
+ - `iplot()` — intelligent plot method mimicking MATLAB's `ao.iplot`:
969
+ - Smart data-type dispatch: `TSData` → linear axes; `FSData` → log-log with automatic
970
+ magnitude/phase subplots for complex data.
971
+ - `Arrangement='stacked'` (default) overlays all objects on the same axes.
972
+ - `Arrangement='subplots'` stacks each object in its own subplot row (single figure).
973
+ - `Arrangement='single'` opens one figure per object.
974
+ - `XScales` / `YScales` — per-axis scale override (`'log'` or `'lin'`);
975
+ a single string applies to all axes.
976
+ - `XRanges` / `YRanges` — per-axis `[min, max]` limits.
977
+ - `LineColors`, `LineStyles`, `LineWidths`, `Markers`, `MarkerSizes` — per-object
978
+ style control; shorter lists cycle; `['all', value]` applies one value to every trace.
979
+ - `MarkerFaceColor`, `MarkerEdgeColor` — independent marker fill and border colours;
980
+ same `['all', colour]` shorthand supported.
981
+ - `Legends='off'` suppresses legends; `Legends=['a', 'b']` overrides labels;
982
+ `LegendLocation` accepts MATLAB location strings (`'NorthEast'`, `'Best'`, …);
983
+ `LegendFontSize` controls font size; `ShowDescriptions=True` appends the object's
984
+ `.description` attribute to the legend label.
985
+ - `Titles` — per-subplot title strings (one per object in subplots/single arrangements).
986
+ - `XLabels` / `YLabels` — override axis label names; data units are still appended.
987
+ - `FigureNames` — set the figure suptitle / window title.
988
+ - `complexPlotType` — controls complex-data display: `'absdeg'` (magnitude + phase in °,
989
+ default), `'absrad'` (magnitude + phase in rad), `'realimag'` (real + imaginary parts).
990
+ - `ShowErrors=True` renders error bars from `ddata`; `ErrorBarType='bar'` (default)
991
+ or `'area'` (shaded band). Explicit per-object bounds via `YerrL`, `YerrU`,
992
+ `XerrL`, `XerrU`. `AUTOERRORS=False` disables automatic `ddata` detection.
993
+ - All keyword names match MATLAB's `iplot` exactly for zero relearning cost.
994
+ - `plotinfo` — per-object style metadata that `iplot()` reads automatically.
995
+ `set_plotinfo(color, linestyle, linewidth, marker, markersize, markerfacecolor,
996
+ markeredgecolor, fillmarkers, include_in_legend, show_errors)` attaches a `PlotInfo`
997
+ to any ltpda object. Priority chain: `iplot()` kwarg > plotinfo field > object loose
998
+ attribute > matplotlib default. Full MATLAB XML round-trip: Python reads MATLAB
999
+ `<Style>` XML on retrieve (all color, linestyle, marker fields parsed into matplotlib
1000
+ equivalents); Python emits exact MATLAB-compatible `<Style>` on submit (Java
1001
+ `Color.getRGB()` decimal encoding).
1002
+ - Richer Python AO processing history — Python history nodes are now as informative
1003
+ as MATLAB's and produce distinct per-operation groups in the MATLAB history browser:
1004
+ - Each operation type gets its own blue cluster label instead of the generic
1005
+ `Python/ltpda` bucket: `ao.ao (Python)` for constructors, `ao.psd (Python)` for
1006
+ spectral estimates, `ao.plus (Python)` for arithmetic, etc.
1007
+ - Constructor params are fully recorded: `FS`, `NSECS`, `YUNITS`, `WAVEFORM` (for
1008
+ `randn` / `sinewave`), `A0`, `F0`, `PHI` (for `sinewave`), `DISTRIBUTION` / `SIGMA`
1009
+ (for `randn`).
1010
+ - DSP functions (`psd`, `logpsd`, `mscohere`, `cohere`, `cpsd`, `tfe`) now record a
1011
+ history node that chains back to the input time-series, capturing `WINDOW`, `NAVS`,
1012
+ `PERCENT_OVERLAP`, `NFFT`, `SCALE`, `DETREND_ORDER` (and `PSLL`, `OLAP`, `BMIN`,
1013
+ `LMIN`, `JDES`, `KDES` for `logpsd`). Previously these functions produced no history
1014
+ at all.
1015
+ - `NoiseGen.generateNoise()` records `NSECS`, `FS`, `MODEL`, `YUNITS`.
1016
+ - `__pow__` records `EXPONENT`.
1017
+ - `set_description(text)` — explicit setter on all ltpda objects (mirrors MATLAB's
1018
+ `setDescription`). The `description` property remains directly assignable; this method
1019
+ adds a consistent `set_*` style for use alongside `set_yaxis_name`, `set_plotinfo`, etc.
1020
+ - Bug fixes:
1021
+ - History `context` attribute was silently dropped when Python read a MATLAB-serialized history
1022
+ node from XML and re-submitted it. MATLAB's history browser uses `context` to render "blue tag"
1023
+ cluster labels; losing it caused all pre-existing history steps to appear untagged after a
1024
+ Python round-trip. Fixed by adding a `_context` field to `HistoryNode` and preserving the
1025
+ attribute through the full read → write cycle.
1026
+ - `proctime` on history nodes drifted by the system UTC offset on every Python round-trip.
1027
+ `_parse_history_root` was creating naive datetimes via `datetime.utcfromtimestamp()`, which
1028
+ `datetime.timestamp()` (in the serialiser) then treated as local time. Switched to
1029
+ UTC-aware datetimes (`datetime.fromtimestamp(..., tz=timezone.utc)`) throughout.
1030
+ - AO `UUID` was not preserved on retrieve: `_parse_ao` discarded the `UUID` attribute from
1031
+ the `<ao>` element, so every re-submit generated a fresh random UUID. Now stamped onto
1032
+ `obj.id` after parsing.
1033
+
934
1034
  ### 0.2.2
935
1035
 
936
1036
  - First pypi.org release