ka9q-python 3.26.0__tar.gz → 3.26.1__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 (146) hide show
  1. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/CHANGELOG.md +12 -0
  2. {ka9q_python-3.26.0/ka9q_python.egg-info → ka9q_python-3.26.1}/PKG-INFO +1 -1
  3. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/status.py +10 -6
  4. {ka9q_python-3.26.0 → ka9q_python-3.26.1/ka9q_python.egg-info}/PKG-INFO +1 -1
  5. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/pyproject.toml +1 -1
  6. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_status_decoder.py +37 -2
  7. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/LICENSE +0 -0
  8. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/MANIFEST.in +0 -0
  9. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/README.md +0 -0
  10. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/API_REFERENCE.md +0 -0
  11. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/ARCHITECTURE.md +0 -0
  12. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/CLI_GUIDE.md +0 -0
  13. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/GETTING_STARTED.md +0 -0
  14. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/INSTALLATION.md +0 -0
  15. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/MULTI_STREAM.md +0 -0
  16. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/POLL-SUPPRESSION-DESIGN.md +0 -0
  17. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/RECIPES.md +0 -0
  18. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/REQUIREMENTS.md +0 -0
  19. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/RTP_TIMING_SUPPORT.md +0 -0
  20. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/SECURITY.md +0 -0
  21. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/TESTING_GUIDE.md +0 -0
  22. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/TUI_GUIDE.md +0 -0
  23. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/audit/2026-08-12-alignment/README.md +0 -0
  24. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/audit/2026-08-12-alignment/clients.md +0 -0
  25. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/audit/2026-08-12-alignment/contract.md +0 -0
  26. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/audit/2026-08-12-alignment/control-surface.md +0 -0
  27. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/audit/2026-08-12-alignment/findings.md +0 -0
  28. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/audit/2026-08-12-alignment/idempotency.md +0 -0
  29. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/audit/2026-08-12-alignment/upstream.md +0 -0
  30. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/superpowers/plans/2026-08-12-upstream-alignment-and-client-compat.md +0 -0
  31. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/superpowers/plans/2026-08-13-audit-remediation.md +0 -0
  32. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/superpowers/plans/2026-08-13-client-side-fixes.md +0 -0
  33. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/docs/superpowers/specs/2026-08-12-upstream-alignment-and-client-compat-design.md +0 -0
  34. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/advanced_features_demo.py +0 -0
  35. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/channel_cleanup_example.py +0 -0
  36. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/codar_oceanography.py +0 -0
  37. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/diagnostics/diagnose_packets.py +0 -0
  38. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/diagnostics/repro_utc_bug.py +0 -0
  39. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/discover_example.py +0 -0
  40. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/grape_integration_example.py +0 -0
  41. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/hf_band_scanner.py +0 -0
  42. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/multi_stream_smoke.py +0 -0
  43. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/rtp_recorder_example.py +0 -0
  44. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/simple_am_radio.py +0 -0
  45. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/spectrum_example.py +0 -0
  46. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/stream_example.py +0 -0
  47. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/superdarn_recorder.py +0 -0
  48. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/test_channel_operations.py +0 -0
  49. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/test_improvements.py +0 -0
  50. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/test_timing_fields.py +0 -0
  51. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/tune.py +0 -0
  52. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/examples/tune_example.py +0 -0
  53. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/__init__.py +0 -0
  54. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/_multicast.py +0 -0
  55. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/addressing.py +0 -0
  56. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/cli.py +0 -0
  57. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/compat.py +0 -0
  58. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/control.py +0 -0
  59. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/discovery.py +0 -0
  60. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/exceptions.py +0 -0
  61. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/managed_stream.py +0 -0
  62. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/monitor.py +0 -0
  63. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/multi_stream.py +0 -0
  64. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/pps_calibrator.py +0 -0
  65. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/recovery_ladder.py +0 -0
  66. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/resequencer.py +0 -0
  67. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/rtp_recorder.py +0 -0
  68. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/slot_clock.py +0 -0
  69. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/spectrum_stream.py +0 -0
  70. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/status_listener.py +0 -0
  71. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/stream.py +0 -0
  72. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/stream_quality.py +0 -0
  73. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/tui.py +0 -0
  74. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/types.py +0 -0
  75. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q/utils.py +0 -0
  76. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q_python.egg-info/SOURCES.txt +0 -0
  77. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q_python.egg-info/dependency_links.txt +0 -0
  78. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q_python.egg-info/entry_points.txt +0 -0
  79. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q_python.egg-info/requires.txt +0 -0
  80. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q_python.egg-info/top_level.txt +0 -0
  81. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/ka9q_radio_compat +0 -0
  82. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/scripts/check_upstream_drift.py +0 -0
  83. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/scripts/gen_client_manifest.py +0 -0
  84. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/scripts/sync_types.py +0 -0
  85. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/setup.cfg +0 -0
  86. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/__init__.py +0 -0
  87. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/conftest.py +0 -0
  88. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_addressing.py +0 -0
  89. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_channel_verification.py +0 -0
  90. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_client_contract.py +0 -0
  91. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_client_id_destination.py +0 -0
  92. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_compat_pin_reachability.py +0 -0
  93. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_create_split_encoding.py +0 -0
  94. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_decode_description.py +0 -0
  95. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_decode_functions.py +0 -0
  96. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_delivered_rtp_start.py +0 -0
  97. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_destination_required.py +0 -0
  98. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_encode_functions.py +0 -0
  99. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_encode_socket.py +0 -0
  100. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_ensure_channel_encoding.py +0 -0
  101. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_filter_edges.py +0 -0
  102. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_gen_client_manifest.py +0 -0
  103. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_idempotency_integration.py +0 -0
  104. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_integration.py +0 -0
  105. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_iq_20khz_f32.py +0 -0
  106. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_keepalive_encoding.py +0 -0
  107. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_lifetime.py +0 -0
  108. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_listen_multicast.py +0 -0
  109. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_managed_stream_recovery.py +0 -0
  110. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_monitor.py +0 -0
  111. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_multicast_helpers.py +0 -0
  112. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_multihomed.py +0 -0
  113. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_multistream_completeness.py +0 -0
  114. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_multistream_gap_storm.py +0 -0
  115. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_multistream_prune.py +0 -0
  116. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_multistream_restore.py +0 -0
  117. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_native_discovery.py +0 -0
  118. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_parameter_vector.py +0 -0
  119. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_parse_rtp_samples_iq.py +0 -0
  120. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_performance_fixes.py +0 -0
  121. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_poll_channel_idle.py +0 -0
  122. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_protocol_compat.py +0 -0
  123. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_recovery_ladder.py +0 -0
  124. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_remove_channel.py +0 -0
  125. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_rtp_recorder.py +0 -0
  126. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_rtp_to_wallclock_deprecated.py +0 -0
  127. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_security_features.py +0 -0
  128. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_set_demod_type.py +0 -0
  129. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_set_lock_warns.py +0 -0
  130. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_set_spectrum.py +0 -0
  131. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_slot_clock.py +0 -0
  132. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_spectrum.py +0 -0
  133. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_ssrc_dest_unit.py +0 -0
  134. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_ssrc_encoding_unit.py +0 -0
  135. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_ssrc_radiod_host_unit.py +0 -0
  136. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_status_listener.py +0 -0
  137. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_status_socket_drain.py +0 -0
  138. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_sync_types.py +0 -0
  139. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_ttl_warning.py +0 -0
  140. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_tune.py +0 -0
  141. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_tune_cli.py +0 -0
  142. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_tune_debug.py +0 -0
  143. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_tune_live.py +0 -0
  144. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_tune_method.py +0 -0
  145. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_upstream_drift.py +0 -0
  146. {ka9q_python-3.26.0 → ka9q_python-3.26.1}/tests/test_zero_block_detection.py +0 -0
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.26.1 (2026-10-01)
4
+
5
+ ### Fixed
6
+
7
+ - **`FrontendStatus.input_power_dbm` now matches radiod's `control.c` term
8
+ for term:** `if_power + lna + mixer + if_gain + |rf_atten| - rf_gain +
9
+ rf_level_cal`. The property had every analog sign inverted. It added
10
+ `rf_gain`, subtracted `rf_atten` and subtracted the LNA/mixer/IF gains. On
11
+ B4's RX888 it read about -5 dBm where `control` shows about -34 dBm, and the
12
+ error swung with the AGC. It also returns `None` when radiod sends
13
+ `rf_level_cal = NaN` (uncalibrated), where `control` shows no dBm figure.
14
+
3
15
  ## 3.25.2 (2026-08-22)
4
16
 
5
17
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ka9q-python
3
- Version: 3.26.0
3
+ Version: 3.26.1
4
4
  Summary: Python interface for ka9q-radio control and monitoring
5
5
  Author-email: Michael Hauan AC0G <ac0g@hauan.org>
6
6
  License: MIT
@@ -88,16 +88,20 @@ class FrontendStatus:
88
88
  """Absolute input power in dBm if the front-end is calibrated."""
89
89
  if self.if_power is None or self.rf_level_cal is None:
90
90
  return None
91
- # control.c: dBm = if_power (dBFS) + rf_level_cal + analog gain chain
92
- analog = 0.0
91
+ if not math.isfinite(self.rf_level_cal): # radiod's "not calibrated"
92
+ return None
93
+ # Term for term what radiod's control.c displays as "Input":
94
+ # if_power + lna + mixer + if_gain + |rf_atten| - rf_gain + rf_level_cal
95
+ # Keep it identical to control.c, even where a sign looks odd.
96
+ dbm = self.if_power + self.rf_level_cal
93
97
  for v in (self.lna_gain, self.mixer_gain, self.if_gain):
94
98
  if v is not None:
95
- analog += v
99
+ dbm += v
96
100
  if self.rf_atten is not None:
97
- analog += self.rf_atten # attenuation is signed positive
101
+ dbm += abs(self.rf_atten)
98
102
  if self.rf_gain is not None:
99
- analog -= self.rf_gain
100
- return self.if_power + self.rf_level_cal - analog
103
+ dbm -= self.rf_gain
104
+ return dbm
101
105
 
102
106
 
103
107
  @dataclass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ka9q-python
3
- Version: 3.26.0
3
+ Version: 3.26.1
4
4
  Summary: Python interface for ka9q-radio control and monitoring
5
5
  Author-email: Michael Hauan AC0G <ac0g@hauan.org>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ka9q-python"
7
- version = "3.26.0"
7
+ version = "3.26.1"
8
8
  description = "Python interface for ka9q-radio control and monitoring"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -102,8 +102,34 @@ def test_decode_frontend_and_gpsdo():
102
102
  assert fe.lna_gain == 20
103
103
  assert fe.rf_level_cal == pytest.approx(-50.0)
104
104
  assert fe.if_power == pytest.approx(-3.0)
105
- # input_power_dbm = if_power + rf_level_cal - (lna+mix+if) = -3 - 50 - 35
106
- assert fe.input_power_dbm == pytest.approx(-88.0)
105
+ # control.c ADDS the analog gains: -3 - 50 + (20 + 10 + 5) = -18
106
+ assert fe.input_power_dbm == pytest.approx(-18.0)
107
+
108
+
109
+ @pytest.mark.parametrize("rf_gain, rf_atten, expected", [
110
+ # B4's RX888, 2026-09-29 01:22Z: radiod's control shows -25.9 dBm.
111
+ (1.4664, 0.0, -25.8197 + 1.4 - 1.4664),
112
+ # More VGA gain means a WEAKER input for the same IF level.
113
+ (14.2, 0.0, -25.8197 + 1.4 - 14.2),
114
+ # Attenuation ahead of the ADC means a STRONGER input.
115
+ (0.0, 10.0, -25.8197 + 1.4 + 10.0),
116
+ ])
117
+ def test_input_power_dbm_rf_gain_and_atten_signs(rf_gain, rf_atten, expected):
118
+ """Mirror radiod control.c: dBm = if_power + |rf_atten| - rf_gain + rf_level_cal.
119
+
120
+ (control.c also adds lna/mixer/if gain; test_decode_frontend_and_gpsdo pins that.)
121
+
122
+ The property once ADDED rf_gain and SUBTRACTED rf_atten, so B4's input read
123
+ about -5 dBm when it was near -34 dBm.
124
+ """
125
+ pkt = _build_packet(
126
+ ("float", StatusType.RF_GAIN, rf_gain),
127
+ ("float", StatusType.RF_ATTEN, rf_atten),
128
+ ("float", StatusType.RF_LEVEL_CAL, 1.4),
129
+ ("float", StatusType.IF_POWER, -25.8197),
130
+ )
131
+ fe: FrontendStatus = decode_status_packet(pkt).frontend
132
+ assert fe.input_power_dbm == pytest.approx(expected, abs=1e-4)
107
133
 
108
134
 
109
135
  def test_decode_pll_fm_spectrum():
@@ -223,3 +249,12 @@ class TestFilterDropsBothDecoders:
223
249
  def test_zero_is_preserved_not_dropped(self):
224
250
  from ka9q.control import decode_status_dict
225
251
  assert decode_status_dict(self._pkt(0))["filter_drops"] == 0
252
+
253
+
254
+ def test_input_power_dbm_none_when_uncalibrated_nan():
255
+ """radiod sends rf_level_cal = NaN when uncalibrated; control.c then shows no dBm."""
256
+ pkt = _build_packet(
257
+ ("float", StatusType.RF_LEVEL_CAL, float("nan")),
258
+ ("float", StatusType.IF_POWER, -25.0),
259
+ )
260
+ assert decode_status_packet(pkt).frontend.input_power_dbm is None
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes