ka9q-python 3.14.1__tar.gz → 3.14.2__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 (106) hide show
  1. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/CHANGELOG.md +21 -0
  2. {ka9q_python-3.14.1/ka9q_python.egg-info → ka9q_python-3.14.2}/PKG-INFO +1 -1
  3. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/docs/API_REFERENCE.md +16 -1
  4. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/__init__.py +1 -1
  5. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/rtp_recorder.py +22 -6
  6. {ka9q_python-3.14.1 → ka9q_python-3.14.2/ka9q_python.egg-info}/PKG-INFO +1 -1
  7. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/pyproject.toml +1 -1
  8. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_rtp_recorder.py +64 -0
  9. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/LICENSE +0 -0
  10. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/MANIFEST.in +0 -0
  11. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/README.md +0 -0
  12. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/docs/ARCHITECTURE.md +0 -0
  13. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/docs/CLI_GUIDE.md +0 -0
  14. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/docs/GETTING_STARTED.md +0 -0
  15. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/docs/INSTALLATION.md +0 -0
  16. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/docs/MULTI_STREAM.md +0 -0
  17. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/docs/RECIPES.md +0 -0
  18. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/docs/RTP_TIMING_SUPPORT.md +0 -0
  19. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/docs/SECURITY.md +0 -0
  20. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/docs/TESTING_GUIDE.md +0 -0
  21. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/docs/TUI_GUIDE.md +0 -0
  22. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/advanced_features_demo.py +0 -0
  23. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/channel_cleanup_example.py +0 -0
  24. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/codar_oceanography.py +0 -0
  25. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/diagnostics/diagnose_packets.py +0 -0
  26. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/diagnostics/repro_utc_bug.py +0 -0
  27. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/discover_example.py +0 -0
  28. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/grape_integration_example.py +0 -0
  29. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/hf_band_scanner.py +0 -0
  30. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/multi_stream_smoke.py +0 -0
  31. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/rtp_recorder_example.py +0 -0
  32. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/simple_am_radio.py +0 -0
  33. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/spectrum_example.py +0 -0
  34. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/stream_example.py +0 -0
  35. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/superdarn_recorder.py +0 -0
  36. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/test_channel_operations.py +0 -0
  37. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/test_improvements.py +0 -0
  38. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/test_timing_fields.py +0 -0
  39. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/tune.py +0 -0
  40. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/examples/tune_example.py +0 -0
  41. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/_multicast.py +0 -0
  42. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/addressing.py +0 -0
  43. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/cli.py +0 -0
  44. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/compat.py +0 -0
  45. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/control.py +0 -0
  46. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/discovery.py +0 -0
  47. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/exceptions.py +0 -0
  48. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/managed_stream.py +0 -0
  49. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/monitor.py +0 -0
  50. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/multi_stream.py +0 -0
  51. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/pps_calibrator.py +0 -0
  52. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/resequencer.py +0 -0
  53. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/spectrum_stream.py +0 -0
  54. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/status.py +0 -0
  55. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/stream.py +0 -0
  56. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/stream_quality.py +0 -0
  57. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/tui.py +0 -0
  58. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/types.py +0 -0
  59. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q/utils.py +0 -0
  60. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q_python.egg-info/SOURCES.txt +0 -0
  61. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q_python.egg-info/dependency_links.txt +0 -0
  62. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q_python.egg-info/entry_points.txt +0 -0
  63. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q_python.egg-info/requires.txt +0 -0
  64. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q_python.egg-info/top_level.txt +0 -0
  65. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/ka9q_radio_compat +0 -0
  66. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/scripts/check_upstream_drift.py +0 -0
  67. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/scripts/sync_types.py +0 -0
  68. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/setup.cfg +0 -0
  69. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/setup.py +0 -0
  70. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/__init__.py +0 -0
  71. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/conftest.py +0 -0
  72. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_addressing.py +0 -0
  73. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_channel_verification.py +0 -0
  74. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_client_id_destination.py +0 -0
  75. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_create_split_encoding.py +0 -0
  76. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_decode_description.py +0 -0
  77. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_decode_functions.py +0 -0
  78. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_encode_functions.py +0 -0
  79. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_encode_socket.py +0 -0
  80. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_ensure_channel_encoding.py +0 -0
  81. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_filter_edges.py +0 -0
  82. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_integration.py +0 -0
  83. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_iq_20khz_f32.py +0 -0
  84. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_lifetime.py +0 -0
  85. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_listen_multicast.py +0 -0
  86. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_managed_stream_recovery.py +0 -0
  87. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_monitor.py +0 -0
  88. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_multicast_helpers.py +0 -0
  89. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_multihomed.py +0 -0
  90. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_native_discovery.py +0 -0
  91. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_performance_fixes.py +0 -0
  92. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_protocol_compat.py +0 -0
  93. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_remove_channel.py +0 -0
  94. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_security_features.py +0 -0
  95. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_spectrum.py +0 -0
  96. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_ssrc_dest_unit.py +0 -0
  97. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_ssrc_encoding_unit.py +0 -0
  98. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_ssrc_radiod_host_unit.py +0 -0
  99. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_status_decoder.py +0 -0
  100. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_ttl_warning.py +0 -0
  101. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_tune.py +0 -0
  102. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_tune_cli.py +0 -0
  103. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_tune_debug.py +0 -0
  104. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_tune_live.py +0 -0
  105. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_tune_method.py +0 -0
  106. {ka9q_python-3.14.1 → ka9q_python-3.14.2}/tests/test_upstream_drift.py +0 -0
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.14.2] - 2026-05-14
4
+
5
+ ### Added
6
+
7
+ - **`rtp_to_wallclock()` gains optional `wallclock_hint_sec` parameter.**
8
+ When supplied, the function uses the hint to disambiguate the 32-bit
9
+ RTP wrap epoch instead of calling `time.time()`. Authority-aware
10
+ callers (those with access to an hf-timestd `rtp_to_utc_offset_ns`)
11
+ can now keep the labeling path off the chrony-disciplined system
12
+ clock, per the METROLOGY.md §4.5 RTP-reference invariant. The hint
13
+ only needs ±period/2 accuracy (≥6 hours at typical sample rates).
14
+ When omitted, the function falls back to `time.time()` for backward
15
+ compatibility — existing callers are unaffected.
16
+
17
+ ### Tests
18
+
19
+ - 3 new in `tests/test_rtp_recorder.py`: hint bypasses `time.time()`
20
+ entirely; hint at a different wrap epoch correctly overrides system
21
+ clock; default path still consults `time.time()` when no hint is
22
+ given.
23
+
3
24
  ## [3.14.1] - 2026-05-14
4
25
 
5
26
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ka9q-python
3
- Version: 3.14.1
3
+ Version: 3.14.2
4
4
  Summary: Python interface for ka9q-radio control and monitoring
5
5
  Home-page: https://github.com/mijahauan/ka9q-python
6
6
  Author: Michael Hauan AC0G
@@ -727,7 +727,11 @@ Dataclass: `packets_received`, `packets_dropped`,
727
727
 
728
728
  ```python
729
729
  parse_rtp_header(data: bytes) -> Optional[RTPHeader]
730
- rtp_to_wallclock(rtp_timestamp: int, channel: ChannelInfo) -> Optional[float]
730
+ rtp_to_wallclock(
731
+ rtp_timestamp: int,
732
+ channel: ChannelInfo,
733
+ wallclock_hint_sec: Optional[float] = None,
734
+ ) -> Optional[float]
731
735
  ```
732
736
 
733
737
  `rtp_to_wallclock()` returns `None` unless `channel.gps_time` and
@@ -735,6 +739,17 @@ rtp_to_wallclock(rtp_timestamp: int, channel: ChannelInfo) -> Optional[float]
735
739
  `channel.chain_delay_correction_ns` is set (see below), it is
736
740
  subtracted from the computed wallclock.
737
741
 
742
+ The `wallclock_hint_sec` parameter (v3.14.2+) lets callers supply an
743
+ approximate UTC reference for 32-bit RTP wrap-epoch disambiguation
744
+ without falling back to `time.time()`. Authority-aware callers
745
+ (e.g. those reading `rtp_to_utc_offset_ns` from an `hf-timestd`
746
+ authority.json) should pass this hint to keep the labeling path off
747
+ the chrony-disciplined system clock (METROLOGY.md §4.5 RTP-reference
748
+ invariant). The hint only needs ±period/2 accuracy (≥6 hours at
749
+ typical sample rates), so even a coarse value is sufficient. When
750
+ omitted, the function falls back to `time.time()` for backward
751
+ compatibility.
752
+
738
753
  ---
739
754
 
740
755
  ## L6 BPSK PPS Calibration
@@ -56,7 +56,7 @@ Lower-level usage (explicit control):
56
56
  )
57
57
  print(f"Created channel with SSRC: {ssrc}")
58
58
  """
59
- __version__ = '3.14.1'
59
+ __version__ = '3.14.2'
60
60
  __author__ = 'Michael Hauan AC0G'
61
61
 
62
62
  from .control import RadiodControl, allocate_ssrc
@@ -124,7 +124,11 @@ def parse_rtp_header(data: bytes) -> Optional[RTPHeader]:
124
124
  )
125
125
 
126
126
 
127
- def rtp_to_wallclock(rtp_timestamp: int, channel: ChannelInfo) -> Optional[float]:
127
+ def rtp_to_wallclock(
128
+ rtp_timestamp: int,
129
+ channel: ChannelInfo,
130
+ wallclock_hint_sec: Optional[float] = None,
131
+ ) -> Optional[float]:
128
132
  """
129
133
  Convert RTP timestamp to Unix wall-clock time
130
134
 
@@ -133,6 +137,15 @@ def rtp_to_wallclock(rtp_timestamp: int, channel: ChannelInfo) -> Optional[float
133
137
  Args:
134
138
  rtp_timestamp: RTP timestamp from packet header
135
139
  channel: ChannelInfo with gps_time, rtp_timesnap, sample_rate
140
+ wallclock_hint_sec: Approximate UTC seconds, used solely to
141
+ disambiguate the 32-bit RTP wrap epoch (see below). Must be
142
+ within ±period/2 of true UTC (period = 2**32 / sample_rate
143
+ seconds, ≥6 hours for typical sample rates). When omitted,
144
+ falls back to ``time.time()`` — convenient but couples the
145
+ result to the host system clock. Callers that have an
146
+ hf-timestd authority offset available should pass it
147
+ explicitly to keep the labeling path off the chrony-disciplined
148
+ system clock (METROLOGY.md §4.5 RTP-reference invariant).
136
149
 
137
150
  Returns:
138
151
  Unix timestamp (seconds) or None if timing info unavailable
@@ -149,10 +162,10 @@ def rtp_to_wallclock(rtp_timestamp: int, channel: ChannelInfo) -> Optional[float
149
162
 
150
163
  We disambiguate by picking the wrap-epoch count ``k`` (full
151
164
  2**32-sample periods elapsed since the snapshot) that places
152
- the resulting wall-clock time closest to the local system
153
- clock. System clock stays within seconds of true UTC even
154
- under hostile conditions, and the wrap ambiguity period is
155
- hours, so this is robust without tight NTP discipline.
165
+ the resulting wall-clock time closest to ``wallclock_hint_sec``
166
+ (or ``time.time()`` if no hint was given). Either source needs
167
+ only ±period/2 accuracy, so this stays robust even when the
168
+ hinting reference is loose.
156
169
 
157
170
  Observed on bee1 2026-05-08: long-running SSRCs caused TSL3
158
171
  SHM samples stuck at the snapshot's wall-clock time, ~12.4 h
@@ -181,7 +194,10 @@ def rtp_to_wallclock(rtp_timestamp: int, channel: ChannelInfo) -> Optional[float
181
194
  # value closest to the system clock. Exact when sys clock is
182
195
  # within ±period/2 of true UTC.
183
196
  period_ns = BILLION * 0x100000000 // channel.sample_rate
184
- sys_now_ns = int(time.time() * BILLION)
197
+ if wallclock_hint_sec is not None:
198
+ sys_now_ns = int(wallclock_hint_sec * BILLION)
199
+ else:
200
+ sys_now_ns = int(time.time() * BILLION)
185
201
  diff_ns = sys_now_ns - base_wall_ns
186
202
  if period_ns > 0:
187
203
  # Round-to-nearest of diff_ns / period_ns (Python `//` is
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ka9q-python
3
- Version: 3.14.1
3
+ Version: 3.14.2
4
4
  Summary: Python interface for ka9q-radio control and monitoring
5
5
  Home-page: https://github.com/mijahauan/ka9q-python
6
6
  Author: Michael Hauan AC0G
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ka9q-python"
7
- version = "3.14.1"
7
+ version = "3.14.2"
8
8
  description = "Python interface for ka9q-radio control and monitoring"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -118,3 +118,67 @@ def test_rtp_to_wallclock_returns_none_when_timing_missing():
118
118
  assert rtp_to_wallclock(1000, channel) is None
119
119
  channel = _channel(48000, None, 1000)
120
120
  assert rtp_to_wallclock(1000, channel) is None
121
+
122
+
123
+ def test_rtp_to_wallclock_hint_bypasses_system_clock():
124
+ """When `wallclock_hint_sec` is provided, time.time() must NOT be
125
+ consulted. This is the RTP-reference invariant: authority-aware
126
+ callers can disambiguate the wrap epoch without coupling to the
127
+ host system clock."""
128
+ sample_rate = 96000
129
+ gps_time_ns = 1234567890000000000
130
+ channel = _channel(sample_rate, gps_time_ns, 1000)
131
+ snapshot_wall = (gps_time_ns + BILLION * (GPS_UTC_OFFSET - 18)) / BILLION
132
+
133
+ period_sec = 0x100000000 / sample_rate
134
+ target_rtp = (1000 + 0x100000000) & 0xFFFFFFFF
135
+ expected = snapshot_wall + period_sec
136
+
137
+ # Pin time.time() to something WAY OFF (1 year earlier) — if the
138
+ # function ignores the hint and consults the system clock anyway,
139
+ # the wrap-epoch math will pick k=0 and the result will be wrong.
140
+ bogus_now = expected - 365 * 86400.0
141
+ with patch("ka9q.rtp_recorder.time.time", return_value=bogus_now) as mock_time:
142
+ result = rtp_to_wallclock(target_rtp, channel, wallclock_hint_sec=expected)
143
+ assert result == pytest.approx(expected, abs=1e-3)
144
+ # Strict: hint path must not call time.time() at all.
145
+ mock_time.assert_not_called()
146
+
147
+
148
+ def test_rtp_to_wallclock_hint_picks_correct_epoch():
149
+ """Hint at a different wrap epoch from the system clock → result
150
+ follows the hint, proving the function uses the hint for k-selection."""
151
+ sample_rate = 12000 # WSPR-band rate; period ≈ 99 hours
152
+ gps_time_ns = 1234567890000000000
153
+ channel = _channel(sample_rate, gps_time_ns, 1000)
154
+ snapshot_wall = (gps_time_ns + BILLION * (GPS_UTC_OFFSET - 18)) / BILLION
155
+ period_sec = 0x100000000 / sample_rate
156
+
157
+ # RTP value aliased to ~snapshot, but actually 2 wraps later.
158
+ target_rtp = (1000 + 2 * 0x100000000) & 0xFFFFFFFF
159
+ expected = snapshot_wall + 2 * period_sec
160
+
161
+ # System clock at the snapshot wall (would pick k=0); hint says
162
+ # ~2 periods later (correct k=2).
163
+ with patch("ka9q.rtp_recorder.time.time", return_value=snapshot_wall):
164
+ result = rtp_to_wallclock(target_rtp, channel, wallclock_hint_sec=expected)
165
+ assert result == pytest.approx(expected, abs=1e-3)
166
+
167
+
168
+ def test_rtp_to_wallclock_hint_omitted_falls_back_to_system_clock():
169
+ """Default path (no hint) preserves legacy behavior for
170
+ backwards compatibility — the hint parameter is purely additive."""
171
+ sample_rate = 48000
172
+ gps_time_ns = 1234567890000000000
173
+ channel = _channel(sample_rate, gps_time_ns, 1000)
174
+ snapshot_wall = (gps_time_ns + BILLION * (GPS_UTC_OFFSET - 18)) / BILLION
175
+ period_sec = 0x100000000 / sample_rate
176
+
177
+ target_rtp = (1000 + 0x100000000) & 0xFFFFFFFF
178
+ expected = snapshot_wall + period_sec
179
+
180
+ # No hint → must consult time.time() to land on k=1.
181
+ with patch("ka9q.rtp_recorder.time.time", return_value=expected) as mock_time:
182
+ result = rtp_to_wallclock(target_rtp, channel)
183
+ assert result == pytest.approx(expected, abs=1e-3)
184
+ mock_time.assert_called()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes