denonavr 1.0.0__tar.gz → 1.0.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 (273) hide show
  1. {denonavr-1.0.0 → denonavr-1.0.1}/PKG-INFO +1 -1
  2. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr/__init__.py +1 -1
  3. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr/api.py +136 -60
  4. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr/decorators.py +5 -32
  5. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr/denonavr.py +1 -1
  6. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr/foundation.py +6 -7
  7. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr/input.py +44 -3
  8. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr.egg-info/PKG-INFO +1 -1
  9. {denonavr-1.0.0 → denonavr-1.0.1}/pyproject.toml +1 -1
  10. {denonavr-1.0.0 → denonavr-1.0.1}/tests/test_denonavr.py +12 -0
  11. {denonavr-1.0.0 → denonavr-1.0.1}/LICENSE +0 -0
  12. {denonavr-1.0.0 → denonavr-1.0.1}/README.md +0 -0
  13. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr/appcommand.py +0 -0
  14. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr/audyssey.py +0 -0
  15. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr/const.py +1 -1
  16. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr/exceptions.py +0 -0
  17. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr/soundmode.py +0 -0
  18. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr/ssdp.py +0 -0
  19. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr/tonecontrol.py +0 -0
  20. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr/volume.py +0 -0
  21. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr.egg-info/SOURCES.txt +0 -0
  22. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr.egg-info/dependency_links.txt +0 -0
  23. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr.egg-info/not-zip-safe +0 -0
  24. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr.egg-info/requires.txt +0 -0
  25. {denonavr-1.0.0 → denonavr-1.0.1}/denonavr.egg-info/top_level.txt +0 -0
  26. {denonavr-1.0.0 → denonavr-1.0.1}/setup.cfg +0 -0
  27. {denonavr-1.0.0 → denonavr-1.0.1}/tests/__init__.py +0 -0
  28. {denonavr-1.0.0 → denonavr-1.0.1}/tests/test_ssdp.py +0 -0
  29. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AV7703-AppCommand-setup-8080.xml +0 -0
  30. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AV7703-AppCommand-update-8080.xml +0 -0
  31. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AV7703-AppCommand-update-soundmode-8080.xml +0 -0
  32. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AV7703-AppCommand-update-tonecontrol-8080.xml +0 -0
  33. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AV7703-Deviceinfo-8080.xml +0 -0
  34. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVC-8500H-AppCommand-setup-8080.xml +0 -0
  35. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVC-8500H-AppCommand-update-8080.xml +0 -0
  36. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVC-8500H-AppCommand-update-soundmode-8080.xml +0 -0
  37. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVC-8500H-AppCommand-update-tonecontrol-8080.xml +0 -0
  38. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVC-8500H-Deviceinfo-8080.xml +0 -0
  39. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVC-X3700H-AppCommand-setup-8080.xml +0 -0
  40. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVC-X3700H-AppCommand-update-8080.xml +0 -0
  41. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVC-X3700H-AppCommand-update-soundmode-8080.xml +0 -0
  42. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVC-X3700H-AppCommand-update-tonecontrol-8080.xml +0 -0
  43. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVC-X3700H-Deviceinfo-8080.xml +0 -0
  44. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1713-AppCommand-setup.xml +0 -0
  45. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1713-AppCommand-update-soundmode.xml +0 -0
  46. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1713-AppCommand-update-tonecontrol.xml +0 -0
  47. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1713-AppCommand-update.xml +0 -0
  48. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1713-Deviceinfo.xml +0 -0
  49. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1713-formMainZone_MainZoneXml.xml +0 -0
  50. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1713-formMainZone_MainZoneXmlStatus.xml +0 -0
  51. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1713-formNetAudio_StatusXml.xml +0 -0
  52. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1713-formTuner_HdXml.xml +0 -0
  53. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1713-formTuner_TunerXml.xml +0 -0
  54. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1713-formZone2_Zone2XmlStatus.xml +0 -0
  55. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1713-formZone3_Zone3XmlStatus.xml +0 -0
  56. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1912-AppCommand-setup.xml +0 -0
  57. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1912-AppCommand-update-soundmode.xml +0 -0
  58. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1912-AppCommand-update-tonecontrol.xml +0 -0
  59. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1912-AppCommand-update.xml +0 -0
  60. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1912-Deviceinfo.xml +0 -0
  61. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1912-formMainZone_MainZoneXml.xml +0 -0
  62. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1912-formMainZone_MainZoneXmlStatus.xml +0 -0
  63. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-1912-formNetAudio_StatusXml.xml +0 -0
  64. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-2312CI-AppCommand-setup.xml +0 -0
  65. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-2312CI-AppCommand-update-soundmode.xml +0 -0
  66. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-2312CI-AppCommand-update-tonecontrol.xml +0 -0
  67. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-2312CI-AppCommand-update.xml +0 -0
  68. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-2312CI-Deviceinfo.xml +0 -0
  69. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-2312CI-formMainZone_MainZoneXml.xml +0 -0
  70. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-2312CI-formMainZone_MainZoneXmlStatus.xml +0 -0
  71. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-2312CI-formNetAudio_StatusXml.xml +0 -0
  72. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3311CI-AppCommand-setup.xml +0 -0
  73. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3311CI-AppCommand-update-soundmode.xml +0 -0
  74. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3311CI-AppCommand-update-tonecontrol.xml +0 -0
  75. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3311CI-AppCommand-update.xml +0 -0
  76. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3311CI-Deviceinfo.xml +0 -0
  77. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3311CI-formMainZone_MainZoneXml.xml +0 -0
  78. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3311CI-formMainZone_MainZoneXmlStatus.xml +0 -0
  79. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3311CI-formNetAudio_StatusXml.xml +0 -0
  80. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3312-AppCommand-setup.xml +0 -0
  81. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3312-AppCommand-update-soundmode.xml +0 -0
  82. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3312-AppCommand-update-tonecontrol.xml +0 -0
  83. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3312-AppCommand-update.xml +0 -0
  84. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3312-Deviceinfo.xml +0 -0
  85. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3312-formMainZone_MainZoneXml.xml +0 -0
  86. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3312-formMainZone_MainZoneXmlStatus.xml +0 -0
  87. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3312-formNetAudio_StatusXml.xml +0 -0
  88. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3312-formTuner_HdXml.xml +0 -0
  89. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3312-formTuner_TunerXml.xml +0 -0
  90. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3312-formZone2_Zone2XmlStatus.xml +0 -0
  91. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3312-formZone3_Zone3XmlStatus.xml +0 -0
  92. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3313-AppCommand-setup.xml +0 -0
  93. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3313-AppCommand-update-soundmode.xml +0 -0
  94. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3313-AppCommand-update-tonecontrol.xml +0 -0
  95. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3313-AppCommand-update.xml +0 -0
  96. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3313-Deviceinfo.xml +0 -0
  97. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3313-formMainZone_MainZoneXml.xml +0 -0
  98. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3313-formMainZone_MainZoneXmlStatus.xml +0 -0
  99. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3313-formNetAudio_StatusXml.xml +0 -0
  100. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3313-formTuner_HdXml.xml +0 -0
  101. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3313-formTuner_TunerXml.xml +0 -0
  102. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3313-formZone2_Zone2XmlStatus.xml +0 -0
  103. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-3313-formZone3_Zone3XmlStatus.xml +0 -0
  104. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-4810-AppCommand-setup.xml +0 -0
  105. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-4810-AppCommand-update-soundmode.xml +0 -0
  106. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-4810-AppCommand-update-tonecontrol.xml +0 -0
  107. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-4810-AppCommand-update.xml +0 -0
  108. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-4810-Deviceinfo.xml +0 -0
  109. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-4810-formMainZone_MainZoneXml.xml +0 -0
  110. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-4810-formMainZone_MainZoneXmlStatus.xml +0 -0
  111. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-4810-formNetAudio_StatusXml.xml +0 -0
  112. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-4810-formTuner_HdXml.xml +0 -0
  113. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-4810-formTuner_TunerXml.xml +0 -0
  114. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-4810-formZone2_Zone2XmlStatus.xml +0 -0
  115. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-4810-formZone3_Zone3XmlStatus.xml +0 -0
  116. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X1100W-AppCommand-setup.xml +0 -0
  117. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X1100W-AppCommand-update-soundmode.xml +0 -0
  118. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X1100W-AppCommand-update-tonecontrol.xml +0 -0
  119. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X1100W-AppCommand-update.xml +0 -0
  120. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X1100W-Deviceinfo.xml +0 -0
  121. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X1100W-formMainZone_MainZoneXml.xml +0 -0
  122. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X1100W-formMainZone_MainZoneXmlStatus.xml +0 -0
  123. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X1100W-formNetAudio_StatusXml.xml +0 -0
  124. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X1100W-formTuner_HdXml.xml +0 -0
  125. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X1100W-formTuner_TunerXml.xml +0 -0
  126. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X1100W-formZone2_Zone2XmlStatus.xml +0 -0
  127. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X1100W-formZone3_Zone3XmlStatus.xml +0 -0
  128. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X1600H_upnp.xml +0 -0
  129. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-2-AppCommand-setup.xml +0 -0
  130. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-2-AppCommand-update-soundmode.xml +0 -0
  131. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-2-AppCommand-update-tonecontrol.xml +0 -0
  132. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-2-AppCommand-update.xml +0 -0
  133. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-2-Deviceinfo.xml +0 -0
  134. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-2-formMainZone_MainZoneXml.xml +0 -0
  135. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-2-formMainZone_MainZoneXmlStatus.xml +0 -0
  136. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-2-formNetAudio_StatusXml.xml +0 -0
  137. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-AppCommand-setup.xml +0 -0
  138. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-AppCommand-update-soundmode.xml +0 -0
  139. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-AppCommand-update-tonecontrol.xml +0 -0
  140. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-AppCommand-update.xml +0 -0
  141. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-Deviceinfo.xml +0 -0
  142. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-formMainZone_MainZoneXml.xml +0 -0
  143. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-formMainZone_MainZoneXmlStatus.xml +0 -0
  144. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-formNetAudio_StatusXml.xml +0 -0
  145. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-formTuner_HdXml.xml +0 -0
  146. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-formTuner_TunerXml.xml +0 -0
  147. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-formZone2_Zone2XmlStatus.xml +0 -0
  148. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2000-formZone3_Zone3XmlStatus.xml +0 -0
  149. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2100W-2-AppCommand-setup.xml +0 -0
  150. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2100W-2-AppCommand-update-soundmode.xml +0 -0
  151. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2100W-2-AppCommand-update-tonecontrol.xml +0 -0
  152. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2100W-2-AppCommand-update.xml +0 -0
  153. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2100W-2-Deviceinfo.xml +0 -0
  154. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2100W-2-formMainZone_MainZoneXml.xml +0 -0
  155. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2100W-2-formMainZone_MainZoneXmlStatus.xml +0 -0
  156. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2100W-2-formNetAudio_StatusXml.xml +0 -0
  157. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2100W-AppCommand-setup.xml +0 -0
  158. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2100W-AppCommand-update-soundmode.xml +0 -0
  159. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2100W-AppCommand-update-tonecontrol.xml +0 -0
  160. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2100W-AppCommand-update.xml +0 -0
  161. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2100W-formMainZone_MainZoneXml.xml +0 -0
  162. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2100W-formMainZone_MainZoneXmlStatus.xml +0 -0
  163. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X2100W-formNetAudio_StatusXml.xml +0 -0
  164. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4000-AppCommand-setup.xml +0 -0
  165. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4000-AppCommand-update-soundmode.xml +0 -0
  166. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4000-AppCommand-update-tonecontrol.xml +0 -0
  167. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4000-AppCommand-update.xml +0 -0
  168. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4000-Deviceinfo.xml +0 -0
  169. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4000-formMainZone_MainZoneXml.xml +0 -0
  170. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4000-formMainZone_MainZoneXmlStatus.xml +0 -0
  171. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4000-formNetAudio_StatusXml.xml +0 -0
  172. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4000-formTuner_HdXml.xml +0 -0
  173. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4000-formTuner_TunerXml.xml +0 -0
  174. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4000-formZone2_Zone2XmlStatus.xml +0 -0
  175. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4000-formZone3_Zone3XmlStatus.xml +0 -0
  176. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4100W-AppCommand-setup.xml +0 -0
  177. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4100W-AppCommand-update-soundmode.xml +0 -0
  178. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4100W-AppCommand-update-tonecontrol.xml +0 -0
  179. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4100W-AppCommand-update.xml +0 -0
  180. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4100W-Deviceinfo.xml +0 -0
  181. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4100W-formMainZone_MainZoneXml.xml +0 -0
  182. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4100W-formMainZone_MainZoneXmlStatus.xml +0 -0
  183. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4100W-formNetAudio_StatusXml.xml +0 -0
  184. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4300H-AppCommand-setup-8080.xml +0 -0
  185. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4300H-AppCommand-update-8080.xml +0 -0
  186. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4300H-AppCommand-update-soundmode-8080.xml +0 -0
  187. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4300H-AppCommand-update-tonecontrol-8080.xml +0 -0
  188. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/AVR-X4300H-Deviceinfo-8080.xml +0 -0
  189. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-2-AppCommand-setup.xml +0 -0
  190. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-2-AppCommand-update-soundmode.xml +0 -0
  191. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-2-AppCommand-update-tonecontrol.xml +0 -0
  192. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-2-AppCommand-update.xml +0 -0
  193. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-2-Deviceinfo.xml +0 -0
  194. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-2-formMainZone_MainZoneXml.xml +0 -0
  195. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-2-formMainZone_MainZoneXmlStatus.xml +0 -0
  196. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-2-formNetAudio_StatusXml.xml +0 -0
  197. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-2-formTuner_HdXml.xml +0 -0
  198. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-2-formTuner_TunerXml.xml +0 -0
  199. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-AppCommand-setup.xml +0 -0
  200. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-AppCommand-update-soundmode.xml +0 -0
  201. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-AppCommand-update-tonecontrol.xml +0 -0
  202. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-AppCommand-update.xml +0 -0
  203. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-Deviceinfo.xml +0 -0
  204. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-formMainZone_MainZoneXml.xml +0 -0
  205. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-formMainZone_MainZoneXmlStatus.xml +0 -0
  206. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-formNetAudio_StatusXml.xml +0 -0
  207. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-formTuner_HdXml.xml +0 -0
  208. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR510-formTuner_TunerXml.xml +0 -0
  209. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR603-AppCommand-setup.xml +0 -0
  210. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR603-AppCommand-update-soundmode.xml +0 -0
  211. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR603-AppCommand-update-tonecontrol.xml +0 -0
  212. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR603-AppCommand-update.xml +0 -0
  213. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR603-Deviceinfo.xml +0 -0
  214. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR603-formMainZone_MainZoneXml.xml +0 -0
  215. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR603-formMainZone_MainZoneXmlStatus.xml +0 -0
  216. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR603-formNetAudio_StatusXml.xml +0 -0
  217. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR603-formTuner_HdXml.xml +0 -0
  218. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR603-formTuner_TunerXml.xml +0 -0
  219. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR603-formZone2_Zone2XmlStatus.xml +0 -0
  220. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-CR603-formZone3_Zone3XmlStatus.xml +0 -0
  221. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-RC610-AppCommand-setup.xml +0 -0
  222. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-RC610-AppCommand-update-soundmode.xml +0 -0
  223. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-RC610-AppCommand-update-tonecontrol.xml +0 -0
  224. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-RC610-AppCommand-update.xml +0 -0
  225. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-RC610-Deviceinfo.xml +0 -0
  226. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-RC610-formMainZone_MainZoneXml.xml +0 -0
  227. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-RC610-formMainZone_MainZoneXmlStatus.xml +0 -0
  228. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-RC610-formNetAudio_StatusXml.xml +0 -0
  229. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/M-RC610-formTuner_TunerXml.xml +0 -0
  230. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/NR1604-AppCommand-setup.xml +0 -0
  231. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/NR1604-AppCommand-update-soundmode.xml +0 -0
  232. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/NR1604-AppCommand-update-tonecontrol.xml +0 -0
  233. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/NR1604-AppCommand-update.xml +0 -0
  234. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/NR1604-Deviceinfo.xml +0 -0
  235. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/NR1604-formMainZone_MainZoneXml.xml +0 -0
  236. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/NR1604-formMainZone_MainZoneXmlStatus.xml +0 -0
  237. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/NR1604-formNetAudio_StatusXml.xml +0 -0
  238. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/NR1604-formTuner_HdXml.xml +0 -0
  239. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/NR1604-formTuner_TunerXml.xml +0 -0
  240. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/NR1604-formZone2_Zone2XmlStatus.xml +0 -0
  241. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/NR1604-formZone3_Zone3XmlStatus.xml +0 -0
  242. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/NR1609-AppCommand-setup-8080.xml +0 -0
  243. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/NR1609-AppCommand-update-8080.xml +0 -0
  244. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/NR1609-AppCommand-update-soundmode-8080.xml +0 -0
  245. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/NR1609-AppCommand-update-tonecontrol-8080.xml +0 -0
  246. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/NR1609-Deviceinfo-8080.xml +0 -0
  247. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR5008-AppCommand-setup.xml +0 -0
  248. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR5008-AppCommand-update-soundmode.xml +0 -0
  249. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR5008-AppCommand-update-tonecontrol.xml +0 -0
  250. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR5008-AppCommand-update.xml +0 -0
  251. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR5008-Deviceinfo.xml +0 -0
  252. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR5008-formMainZone_MainZoneXml.xml +0 -0
  253. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR5008-formMainZone_MainZoneXmlStatus.xml +0 -0
  254. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR5008-formNetAudio_StatusXml.xml +0 -0
  255. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR5008-formTuner_HdXml.xml +0 -0
  256. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR5008-formTuner_TunerXml.xml +0 -0
  257. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR6011-AppCommand-setup.xml +0 -0
  258. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR6011-AppCommand-update-soundmode.xml +0 -0
  259. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR6011-AppCommand-update-tonecontrol.xml +0 -0
  260. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR6011-AppCommand-update.xml +0 -0
  261. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR6011-Deviceinfo.xml +0 -0
  262. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR6011-formMainZone_MainZoneXml.xml +0 -0
  263. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR6011-formMainZone_MainZoneXmlStatus.xml +0 -0
  264. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR6011-formNetAudio_StatusXml.xml +0 -0
  265. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR6011-formTuner_HdXml.xml +0 -0
  266. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR6011-formTuner_TunerXml.xml +0 -0
  267. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR6011-formZone2_Zone2XmlStatus.xml +0 -0
  268. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR6011-formZone3_Zone3XmlStatus.xml +0 -0
  269. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR6012-AppCommand-setup-8080.xml +0 -0
  270. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR6012-AppCommand-update-8080.xml +0 -0
  271. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR6012-AppCommand-update-soundmode-8080.xml +0 -0
  272. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR6012-AppCommand-update-tonecontrol-8080.xml +0 -0
  273. {denonavr-1.0.0 → denonavr-1.0.1}/tests/xml/SR6012-Deviceinfo-8080.xml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: denonavr
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: Automation Library for Denon AVR receivers
5
5
  Author-email: Oliver Goetz <scarface@mywoh.de>
6
6
  License: MIT
@@ -17,7 +17,7 @@ from .ssdp import async_identify_denonavr_receivers
17
17
  logging.getLogger(__name__).addHandler(logging.NullHandler())
18
18
 
19
19
  __title__ = "denonavr"
20
- __version__ = "1.0.0"
20
+ __version__ = "1.0.1"
21
21
 
22
22
 
23
23
  async def async_discover():
@@ -31,7 +31,8 @@ from typing import (
31
31
 
32
32
  import attr
33
33
  import httpx
34
- from defusedxml.ElementTree import fromstring
34
+ from defusedxml import DefusedXmlException
35
+ from defusedxml.ElementTree import ParseError, fromstring
35
36
 
36
37
  from .appcommand import AppCommandCmd
37
38
  from .const import (
@@ -83,16 +84,86 @@ def telnet_event_map_factory() -> Dict[str, List]:
83
84
  return dict(event_map)
84
85
 
85
86
 
86
- @attr.s(auto_attribs=True, hash=False, on_setattr=DENON_ATTR_SETATTR)
87
+ @attr.s(auto_attribs=True, hash=False)
88
+ class HTTPXAsyncClient:
89
+ """Perform cached HTTP calls with httpx.AsyncClient."""
90
+
91
+ client_getter: Callable[[], httpx.AsyncClient] = attr.ib(
92
+ validator=attr.validators.is_callable(),
93
+ default=get_default_async_client,
94
+ init=False,
95
+ )
96
+
97
+ def __hash__(self) -> int:
98
+ """Hash the class using its ID that caching works."""
99
+ return id(self)
100
+
101
+ @cache_result
102
+ @async_handle_receiver_exceptions
103
+ async def async_get(
104
+ self,
105
+ url: str,
106
+ timeout: float,
107
+ read_timeout: float,
108
+ *,
109
+ cache_id: Hashable = None,
110
+ ) -> httpx.Response:
111
+ """Call GET endpoint of Denon AVR receiver asynchronously."""
112
+ client = self.client_getter()
113
+ try:
114
+ res = await client.get(
115
+ url, timeout=httpx.Timeout(timeout, read=read_timeout)
116
+ )
117
+ res.raise_for_status()
118
+ finally:
119
+ # Close the default AsyncClient but keep custom clients open
120
+ if self.is_default_async_client():
121
+ await client.aclose()
122
+
123
+ return res
124
+
125
+ @cache_result
126
+ @async_handle_receiver_exceptions
127
+ async def async_post(
128
+ self,
129
+ url: str,
130
+ timeout: float,
131
+ read_timeout: float,
132
+ *,
133
+ content: Optional[bytes] = None,
134
+ data: Optional[Dict] = None,
135
+ cache_id: Hashable = None,
136
+ ) -> httpx.Response:
137
+ """Call GET endpoint of Denon AVR receiver asynchronously."""
138
+ client = self.client_getter()
139
+ try:
140
+ res = await client.post(
141
+ url,
142
+ content=content,
143
+ data=data,
144
+ timeout=httpx.Timeout(timeout, read=read_timeout),
145
+ )
146
+ res.raise_for_status()
147
+ finally:
148
+ # Close the default AsyncClient but keep custom clients open
149
+ if self.is_default_async_client():
150
+ await client.aclose()
151
+
152
+ return res
153
+
154
+ def is_default_async_client(self) -> bool:
155
+ """Check if default httpx.AsyncClient getter is used."""
156
+ return self.client_getter is get_default_async_client
157
+
158
+
159
+ @attr.s(auto_attribs=True, on_setattr=DENON_ATTR_SETATTR)
87
160
  class DenonAVRApi:
88
161
  """Perform API calls to Denon AVR REST interface."""
89
162
 
90
163
  host: str = attr.ib(converter=str, default="localhost")
91
164
  port: int = attr.ib(converter=int, default=80)
92
- timeout: httpx.Timeout = attr.ib(
93
- validator=attr.validators.instance_of(httpx.Timeout),
94
- default=httpx.Timeout(2.0, read=15.0),
95
- )
165
+ timeout: float = attr.ib(converter=float, default=2.0)
166
+ read_timeout: float = attr.ib(converter=float, default=15.0)
96
167
  _appcommand_update_tags: Tuple[AppCommandCmd] = attr.ib(
97
168
  validator=attr.validators.deep_iterable(
98
169
  attr.validators.instance_of(AppCommandCmd),
@@ -107,23 +178,18 @@ class DenonAVRApi:
107
178
  ),
108
179
  default=attr.Factory(tuple),
109
180
  )
110
- async_client_getter: Callable[[], httpx.AsyncClient] = attr.ib(
111
- validator=attr.validators.is_callable(),
112
- default=get_default_async_client,
181
+ httpx_async_client: HTTPXAsyncClient = attr.ib(
182
+ validator=attr.validators.instance_of(HTTPXAsyncClient),
183
+ default=attr.Factory(HTTPXAsyncClient),
113
184
  init=False,
114
185
  )
115
186
 
116
- def __hash__(self) -> int:
117
- """
118
- Hash the class in a custom way that caching works.
119
-
120
- It should react on changes of host and port.
121
- """
122
- return hash((self.host, self.port))
123
-
124
- @async_handle_receiver_exceptions
125
187
  async def async_get(
126
- self, request: str, port: Optional[int] = None
188
+ self,
189
+ request: str,
190
+ *,
191
+ port: Optional[int] = None,
192
+ cache_id: Hashable = None,
127
193
  ) -> httpx.Response:
128
194
  """Call GET endpoint of Denon AVR receiver asynchronously."""
129
195
  # Use default port of the receiver if no different port is specified
@@ -131,24 +197,18 @@ class DenonAVRApi:
131
197
 
132
198
  endpoint = f"http://{self.host}:{port}{request}"
133
199
 
134
- client = self.async_client_getter()
135
- try:
136
- res = await client.get(endpoint, timeout=self.timeout)
137
- res.raise_for_status()
138
- finally:
139
- # Close the default AsyncClient but keep custom clients open
140
- if self.is_default_async_client():
141
- await client.aclose()
142
-
143
- return res
200
+ return await self.httpx_async_client.async_get(
201
+ endpoint, self.timeout, self.read_timeout, cache_id=cache_id
202
+ )
144
203
 
145
- @async_handle_receiver_exceptions
146
204
  async def async_post(
147
205
  self,
148
206
  request: str,
207
+ *,
149
208
  content: Optional[bytes] = None,
150
209
  data: Optional[Dict] = None,
151
210
  port: Optional[int] = None,
211
+ cache_id: Hashable = None,
152
212
  ) -> httpx.Response:
153
213
  """Call POST endpoint of Denon AVR receiver asynchronously."""
154
214
  # Use default port of the receiver if no different port is specified
@@ -156,20 +216,15 @@ class DenonAVRApi:
156
216
 
157
217
  endpoint = f"http://{self.host}:{port}{request}"
158
218
 
159
- client = self.async_client_getter()
160
- try:
161
- res = await client.post(
162
- endpoint, content=content, data=data, timeout=self.timeout
163
- )
164
- res.raise_for_status()
165
- finally:
166
- # Close the default AsyncClient but keep custom clients open
167
- if self.is_default_async_client():
168
- await client.aclose()
169
-
170
- return res
219
+ return await self.httpx_async_client.async_post(
220
+ endpoint,
221
+ self.timeout,
222
+ self.read_timeout,
223
+ content=content,
224
+ data=data,
225
+ cache_id=cache_id,
226
+ )
171
227
 
172
- @async_handle_receiver_exceptions
173
228
  async def async_get_command(self, request: str) -> str:
174
229
  """Send HTTP GET command to Denon AVR receiver asynchronously."""
175
230
  # HTTP GET to endpoint
@@ -177,34 +232,46 @@ class DenonAVRApi:
177
232
  # Return text
178
233
  return res.text
179
234
 
180
- @cache_result
181
- @async_handle_receiver_exceptions
182
235
  async def async_get_xml(
183
- self, request: str, cache_id: Hashable = None
236
+ self, request: str, *, cache_id: Hashable = None
184
237
  ) -> ET.Element:
185
238
  """Return XML data from HTTP GET endpoint asynchronously."""
186
239
  # HTTP GET to endpoint
187
- res = await self.async_get(request)
240
+ res = await self.async_get(request, cache_id=cache_id)
188
241
  # create ElementTree
189
- xml_root = fromstring(res.text)
242
+ try:
243
+ xml_root = fromstring(res.text)
244
+ except (
245
+ ET.ParseError,
246
+ DefusedXmlException,
247
+ ParseError,
248
+ UnicodeDecodeError,
249
+ ) as err:
250
+ raise AvrInvalidResponseError(f"XMLParseError: {err}", request) from err
190
251
  # Check validity of XML
191
252
  self.check_xml_validity(request, xml_root)
192
253
  # Return ElementTree element
193
254
  return xml_root
194
255
 
195
- @cache_result
196
- @async_handle_receiver_exceptions
197
256
  async def async_post_appcommand(
198
- self, request: str, cmds: Tuple[AppCommandCmd], cache_id: Hashable = None
257
+ self, request: str, cmds: Tuple[AppCommandCmd], *, cache_id: Hashable = None
199
258
  ) -> ET.Element:
200
259
  """Return XML from Appcommand(0300) endpoint asynchronously."""
201
260
  # Prepare XML body for POST call
202
261
  content = self.prepare_appcommand_body(cmds)
203
262
  _LOGGER.debug("Content for %s endpoint: %s", request, content)
204
263
  # HTTP POST to endpoint
205
- res = await self.async_post(request, content=content)
264
+ res = await self.async_post(request, content=content, cache_id=cache_id)
206
265
  # create ElementTree
207
- xml_root = fromstring(res.text)
266
+ try:
267
+ xml_root = fromstring(res.text)
268
+ except (
269
+ ET.ParseError,
270
+ DefusedXmlException,
271
+ ParseError,
272
+ UnicodeDecodeError,
273
+ ) as err:
274
+ raise AvrInvalidResponseError(f"XMLParseError: {err}", request) from err
208
275
  # Check validity of XML
209
276
  self.check_xml_validity(request, xml_root)
210
277
  # Add query tags to result
@@ -350,10 +417,6 @@ class DenonAVRApi:
350
417
 
351
418
  return body_bytes
352
419
 
353
- def is_default_async_client(self) -> bool:
354
- """Check if default httpx.AsyncClient getter is used."""
355
- return self.async_client_getter is get_default_async_client
356
-
357
420
 
358
421
  class DenonAVRTelnetProtocol(asyncio.Protocol):
359
422
  """Protocol for the Denon AVR Telnet interface."""
@@ -481,8 +544,8 @@ class DenonAVRTelnetApi:
481
544
  "%s: Connection failed on telnet reconnect: %s", self.host, err
482
545
  )
483
546
  raise AvrNetworkError(f"OSError: {err}", "telnet connect") from err
484
- _LOGGER.debug("%s: telnet connection established", self.host)
485
547
  self._protocol = cast(DenonAVRTelnetProtocol, transport_protocol[1])
548
+ _LOGGER.debug("%s: telnet connection established", self.host)
486
549
  self._connection_enabled = True
487
550
  self._last_message_time = time.monotonic()
488
551
  self._schedule_monitor()
@@ -543,7 +606,8 @@ class DenonAVRTelnetApi:
543
606
  self._stop_monitor()
544
607
  if not self._connection_enabled:
545
608
  return
546
- self._reconnect_task = asyncio.create_task(self._async_reconnect())
609
+ if self._reconnect_task is None:
610
+ self._reconnect_task = asyncio.create_task(self._async_reconnect())
547
611
 
548
612
  async def async_disconnect(self) -> None:
549
613
  """Close the connection to the receiver asynchronously."""
@@ -581,6 +645,12 @@ class DenonAVRTelnetApi:
581
645
  )
582
646
  except AvrNetworkError as err:
583
647
  _LOGGER.debug("%s: %s", self.host, err)
648
+ except AvrProcessingError as err:
649
+ _LOGGER.debug(
650
+ "%s: Failed updating state on telnet reconnect: %s",
651
+ self.host,
652
+ err,
653
+ )
584
654
  except Exception as err: # pylint: disable=broad-except
585
655
  _LOGGER.error(
586
656
  "%s: Unexpected exception on telnet reconnect",
@@ -589,11 +659,13 @@ class DenonAVRTelnetApi:
589
659
  )
590
660
  else:
591
661
  _LOGGER.info("%s: Telnet reconnected", self.host)
592
- return
662
+ break
593
663
 
594
664
  await asyncio.sleep(backoff)
595
665
  backoff = min(30.0, backoff * 2)
596
666
 
667
+ self._reconnect_task = None
668
+
597
669
  def register_callback(
598
670
  self, event: str, callback: Callable[[str, str, str], Awaitable[None]]
599
671
  ) -> None:
@@ -604,6 +676,8 @@ class DenonAVRTelnetApi:
604
676
 
605
677
  if event not in self._callbacks.keys():
606
678
  self._callbacks[event] = []
679
+ elif callback in self._callbacks[event]:
680
+ return
607
681
  self._callbacks[event].append(callback)
608
682
 
609
683
  def unregister_callback(
@@ -618,6 +692,8 @@ class DenonAVRTelnetApi:
618
692
  self, callback: Callable[[str], Awaitable[None]]
619
693
  ) -> None:
620
694
  """Register a callback handler for raw telnet messages."""
695
+ if callback in self._raw_callbacks:
696
+ return
621
697
  self._raw_callbacks.append(callback)
622
698
 
623
699
  def _unregister_raw_callback(
@@ -10,14 +10,11 @@ This module implements the REST API to Denon AVR receivers.
10
10
  import inspect
11
11
  import logging
12
12
  import time
13
- import xml.etree.ElementTree as ET
14
13
  from functools import wraps
15
14
  from typing import Callable, TypeVar
16
15
 
17
16
  import httpx
18
17
  from asyncstdlib import lru_cache
19
- from defusedxml import DefusedXmlException
20
- from defusedxml.ElementTree import ParseError
21
18
 
22
19
  from .exceptions import (
23
20
  AvrForbiddenError,
@@ -33,12 +30,7 @@ AnyT = TypeVar("AnyT")
33
30
 
34
31
 
35
32
  def async_handle_receiver_exceptions(func: Callable[..., AnyT]) -> Callable[..., AnyT]:
36
- """
37
- Handle exceptions raised when calling a Denon AVR endpoint asynchronously.
38
-
39
- The decorated function must either have a string variable as second
40
- argument or as "request" keyword argument.
41
- """
33
+ """Handle exceptions raised when calling a Denon AVR endpoint asynchronously."""
42
34
 
43
35
  @wraps(func)
44
36
  async def wrapper(*args, **kwargs):
@@ -64,48 +56,29 @@ def async_handle_receiver_exceptions(func: Callable[..., AnyT]) -> Callable[...,
64
56
  raise AvrInvalidResponseError(
65
57
  f"RemoteProtocolError: {err}", err.request
66
58
  ) from err
67
- except (
68
- ET.ParseError,
69
- DefusedXmlException,
70
- ParseError,
71
- UnicodeDecodeError,
72
- ) as err:
73
- _LOGGER.debug(
74
- "Defusedxml parse error on request %s: %s", (args, kwargs), err
75
- )
76
- raise AvrInvalidResponseError(
77
- f"XMLParseError: {err}", (args, kwargs)
78
- ) from err
79
59
 
80
60
  return wrapper
81
61
 
82
62
 
83
63
  def cache_result(func: Callable[..., AnyT]) -> Callable[..., AnyT]:
84
64
  """
85
- Decorate a function to cache its results with an lru_cache of maxsize 16.
65
+ Decorate a function to cache its results with an lru_cache of maxsize 32.
86
66
 
87
67
  This decorator also sets an "cache_id" keyword argument if it is not set yet.
88
- When an exception occurs it clears lru_cache to prevent memory leaks in
89
- home-assistant when receiver instances are created and deleted right
90
- away in case the device is offline on setup.
91
68
  """
92
69
  if inspect.signature(func).parameters.get("cache_id") is None:
93
70
  raise AttributeError(
94
71
  f"Function {func} does not have a 'cache_id' keyword parameter"
95
72
  )
96
73
 
97
- lru_decorator = lru_cache(maxsize=16)
74
+ lru_decorator = lru_cache(maxsize=32)
98
75
  cached_func = lru_decorator(func)
99
76
 
100
77
  @wraps(func)
101
78
  async def wrapper(*args, **kwargs):
102
79
  if kwargs.get("cache_id") is None:
103
80
  kwargs["cache_id"] = time.time()
104
- try:
105
- return await cached_func(*args, **kwargs)
106
- except Exception as err:
107
- _LOGGER.debug("Exception raised, clearing cache: %s", err)
108
- cached_func.cache_clear()
109
- raise
81
+
82
+ return await cached_func(*args, **kwargs)
110
83
 
111
84
  return wrapper
@@ -519,7 +519,7 @@ class DenonAVR(DenonAVRFoundation):
519
519
  """
520
520
  if not callable(async_client_getter):
521
521
  raise AvrCommandError("Provided object is not callable")
522
- self._device.api.async_client_getter = async_client_getter
522
+ self._device.api.httpx_async_client.client_getter = async_client_getter
523
523
 
524
524
  async def async_dynamic_eq_off(self) -> None:
525
525
  """Turn DynamicEQ off."""
@@ -15,7 +15,6 @@ from copy import deepcopy
15
15
  from typing import Dict, List, Optional, Union
16
16
 
17
17
  import attr
18
- import httpx
19
18
 
20
19
  from .api import DenonAVRApi, DenonAVRTelnetApi
21
20
  from .appcommand import AppCommandCmd, AppCommands
@@ -146,15 +145,15 @@ class DenonAVRDeviceInfo:
146
145
  _LOGGER.debug("Starting device setup")
147
146
  # Reduce read timeout during receiver identification
148
147
  # deviceinfo endpoint takes very long to return 404
149
- timeout = self.api.timeout
150
- self.api.timeout = httpx.Timeout(self.api.timeout.connect)
148
+ read_timeout = self.api.read_timeout
149
+ self.api.read_timeout = self.api.timeout
151
150
  try:
152
151
  _LOGGER.debug("Identifying receiver")
153
152
  await self.async_identify_receiver()
154
153
  _LOGGER.debug("Getting device info")
155
154
  await self.async_get_device_info()
156
155
  finally:
157
- self.api.timeout = timeout
156
+ self.api.read_timeout = read_timeout
158
157
  _LOGGER.debug("Identifying update method")
159
158
  await self.async_identify_update_method()
160
159
 
@@ -323,7 +322,7 @@ class DenonAVRDeviceInfo:
323
322
  self._set_friendly_name(xml)
324
323
 
325
324
  async def async_verify_avr_2016_update_method(
326
- self, cache_id: Hashable = None
325
+ self, *, cache_id: Hashable = None
327
326
  ) -> None:
328
327
  """Verify if avr 2016 update method is working."""
329
328
  # Nothing to do if Appcommand.xml interface is not supported
@@ -833,9 +832,9 @@ def set_api_timeout(
833
832
  ) -> float:
834
833
  """Change API timeout on timeout changes too."""
835
834
  # First change _device.api.host then return value
836
- timeout = httpx.Timeout(value, read=max(value, 15.0))
837
835
  # pylint: disable=protected-access
838
- instance._device.api.timeout = timeout
836
+ instance._device.api.timeout = value
837
+ instance._device.api.read_timeout = max(value, 15.0)
839
838
  instance._device.telnet_api.timeout = value
840
839
  return value
841
840
 
@@ -168,6 +168,13 @@ class DenonAVRInput(DenonAVRFoundation):
168
168
  converter=attr.converters.optional(str), default=None
169
169
  )
170
170
 
171
+ _renamed_sources_warnings: Set[Tuple[str, str]] = attr.ib(
172
+ validator=attr.validators.deep_iterable(
173
+ attr.validators.instance_of(tuple), attr.validators.instance_of(set)
174
+ ),
175
+ default=attr.Factory(set),
176
+ )
177
+
171
178
  # Update tags for attributes
172
179
  # AppCommand.xml interface
173
180
  appcommand_attrs = {AppCommands.GetAllZoneSource: None}
@@ -516,6 +523,8 @@ class DenonAVRInput(DenonAVRFoundation):
516
523
  except AttributeError:
517
524
  continue
518
525
 
526
+ self._replace_duplicate_sources(renamed_sources)
527
+
519
528
  return (renamed_sources, deleted_sources)
520
529
 
521
530
  async def async_get_changed_sources_status_xml(
@@ -604,6 +613,8 @@ class DenonAVRInput(DenonAVRFoundation):
604
613
  except IndexError:
605
614
  _LOGGER.error("List of deleted sources incomplete, continuing anyway")
606
615
 
616
+ self._replace_duplicate_sources(renamed_sources)
617
+
607
618
  return (renamed_sources, deleted_sources)
608
619
 
609
620
  async def async_update_inputfuncs(
@@ -859,10 +870,13 @@ class DenonAVRInput(DenonAVRFoundation):
859
870
  async def _async_test_image_accessible(self) -> None:
860
871
  """Test if image URL is accessible."""
861
872
  if self._image_available is None and self._image_url is not None:
862
- client = self._device.api.async_client_getter()
873
+ client = self._device.api.httpx_async_client.client_getter()
863
874
  try:
864
875
  res = await client.get(
865
- self._image_url, timeout=self._device.api.timeout
876
+ self._image_url,
877
+ timeout=httpx.Timeout(
878
+ self._device.api.timeout, read=self._device.api.read_timeout
879
+ ),
866
880
  )
867
881
  res.raise_for_status()
868
882
  except httpx.TimeoutException:
@@ -878,7 +892,7 @@ class DenonAVRInput(DenonAVRFoundation):
878
892
  self._image_available = True
879
893
  finally:
880
894
  # Close the default AsyncClient but keep custom clients open
881
- if self._device.api.is_default_async_client():
895
+ if self._device.api.httpx_async_client.is_default_async_client():
882
896
  await client.aclose()
883
897
  # Already tested that image URL is not accessible
884
898
  elif not self._image_available:
@@ -894,6 +908,33 @@ class DenonAVRInput(DenonAVRFoundation):
894
908
  self._station = None
895
909
  self._image_url = None
896
910
 
911
+ def _replace_duplicate_sources(self, sources: Dict[str, str]) -> None:
912
+ """Replace duplicate renamed sources (values) with their original names."""
913
+ seen_values = set()
914
+ duplicate_values = set()
915
+
916
+ for value in sources.values():
917
+ if value in seen_values:
918
+ duplicate_values.add(value)
919
+ seen_values.add(value)
920
+
921
+ for duplicate in duplicate_values:
922
+ for key, value in sources.items():
923
+ if value == duplicate:
924
+ sources[key] = key
925
+
926
+ if (key, value) not in self._renamed_sources_warnings:
927
+ _LOGGER.warning(
928
+ (
929
+ "Input source '%s' is renamed to non-unique name '%s'. "
930
+ "Using original name. Please choose unique names in "
931
+ "your receiver's web-interface"
932
+ ),
933
+ key,
934
+ value,
935
+ )
936
+ self._renamed_sources_warnings.add((key, value))
937
+
897
938
  ##############
898
939
  # Properties #
899
940
  ##############
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: denonavr
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: Automation Library for Denon AVR receivers
5
5
  Author-email: Oliver Goetz <scarface@mywoh.de>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "denonavr"
7
- version = "1.0.0"
7
+ version = "1.0.1"
8
8
  authors = [{name = "Oliver Goetz", email = "scarface@mywoh.de"}]
9
9
  license = {text = "MIT"}
10
10
  description = "Automation Library for Denon AVR receivers"
@@ -153,6 +153,7 @@ class TestMainFunctions:
153
153
  self.future.set_result(True)
154
154
 
155
155
  @pytest.mark.asyncio
156
+ @pytest.mark.httpx_mock(can_send_already_matched_responses=True)
156
157
  async def test_receiver_type(self, httpx_mock: HTTPXMock):
157
158
  """Check that receiver type is determined correctly."""
158
159
  httpx_mock.add_callback(self.custom_matcher)
@@ -172,6 +173,7 @@ class TestMainFunctions:
172
173
  )
173
174
 
174
175
  @pytest.mark.asyncio
176
+ @pytest.mark.httpx_mock(can_send_already_matched_responses=True)
175
177
  async def test_input_func_switch(self, httpx_mock: HTTPXMock):
176
178
  """Switch through all input functions of all tested receivers."""
177
179
  httpx_mock.add_callback(self.custom_matcher)
@@ -188,6 +190,7 @@ class TestMainFunctions:
188
190
  await self.denon.zones[name].async_set_input_func(input_func)
189
191
 
190
192
  @pytest.mark.asyncio
193
+ @pytest.mark.httpx_mock(can_send_already_matched_responses=True)
191
194
  async def test_attributes_not_none(self, httpx_mock: HTTPXMock):
192
195
  """Check that certain attributes are not None."""
193
196
  httpx_mock.add_callback(self.custom_matcher)
@@ -210,6 +213,7 @@ class TestMainFunctions:
210
213
  ), f"State is None for receiver {receiver}"
211
214
 
212
215
  @pytest.mark.asyncio
216
+ @pytest.mark.httpx_mock(can_send_already_matched_responses=True)
213
217
  async def test_sound_mode(self, httpx_mock: HTTPXMock):
214
218
  """Check if a valid sound mode is returned."""
215
219
  httpx_mock.add_callback(self.custom_matcher)
@@ -393,6 +397,7 @@ class TestMainFunctions:
393
397
  await api.async_connect()
394
398
 
395
399
  @pytest.mark.asyncio
400
+ @pytest.mark.httpx_mock(can_send_already_matched_responses=True)
396
401
  async def test_receive_callback_called(self, httpx_mock: HTTPXMock):
397
402
  """Check that the callback is triggered whena message is received."""
398
403
  transport = mock.Mock(is_closing=lambda: False)
@@ -428,6 +433,7 @@ class TestMainFunctions:
428
433
  mock_obj.method.assert_called_once()
429
434
 
430
435
  @pytest.mark.asyncio
436
+ @pytest.mark.httpx_mock(can_send_already_matched_responses=True)
431
437
  async def test_mute_on(self, httpx_mock: HTTPXMock):
432
438
  """Check that mute on is processed."""
433
439
  transport = mock.Mock(is_closing=lambda: False)
@@ -458,6 +464,7 @@ class TestMainFunctions:
458
464
  assert self.denon.muted
459
465
 
460
466
  @pytest.mark.asyncio
467
+ @pytest.mark.httpx_mock(can_send_already_matched_responses=True)
461
468
  async def test_mute_off(self, httpx_mock: HTTPXMock):
462
469
  """Check that mute off is processed."""
463
470
  transport = mock.Mock(is_closing=lambda: False)
@@ -488,6 +495,7 @@ class TestMainFunctions:
488
495
  assert not self.denon.muted
489
496
 
490
497
  @pytest.mark.asyncio
498
+ @pytest.mark.httpx_mock(can_send_already_matched_responses=True)
491
499
  async def test_power_on(self, httpx_mock: HTTPXMock):
492
500
  """Check that power on is processed."""
493
501
  transport = mock.Mock(is_closing=lambda: False)
@@ -518,6 +526,7 @@ class TestMainFunctions:
518
526
  assert self.denon.power == "ON"
519
527
 
520
528
  @pytest.mark.asyncio
529
+ @pytest.mark.httpx_mock(can_send_already_matched_responses=True)
521
530
  async def test_power_off(self, httpx_mock: HTTPXMock):
522
531
  """Check that power off is processed."""
523
532
  transport = mock.Mock(is_closing=lambda: False)
@@ -548,6 +557,7 @@ class TestMainFunctions:
548
557
  assert self.denon.power == "OFF"
549
558
 
550
559
  @pytest.mark.asyncio
560
+ @pytest.mark.httpx_mock(can_send_already_matched_responses=True)
551
561
  async def test_volume_min(self, httpx_mock: HTTPXMock):
552
562
  """Check that minimum volume is processed."""
553
563
  transport = mock.Mock(is_closing=lambda: False)
@@ -578,6 +588,7 @@ class TestMainFunctions:
578
588
  assert self.denon.volume == -80.0
579
589
 
580
590
  @pytest.mark.asyncio
591
+ @pytest.mark.httpx_mock(can_send_already_matched_responses=True)
581
592
  async def test_volume_wholenumber(self, httpx_mock: HTTPXMock):
582
593
  """Check that whole number volume is processed."""
583
594
  transport = mock.Mock(is_closing=lambda: False)
@@ -608,6 +619,7 @@ class TestMainFunctions:
608
619
  assert self.denon.volume == -24.0
609
620
 
610
621
  @pytest.mark.asyncio
622
+ @pytest.mark.httpx_mock(can_send_already_matched_responses=True)
611
623
  async def test_volume_fraction(self, httpx_mock: HTTPXMock):
612
624
  """Check that fractional volume is processed."""
613
625
  transport = mock.Mock(is_closing=lambda: False)
File without changes