esphome 2025.9.3__py3-none-any.whl → 2025.10.0b2__py3-none-any.whl

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 (351) hide show
  1. esphome/__main__.py +94 -31
  2. esphome/address_cache.py +142 -0
  3. esphome/automation.py +130 -32
  4. esphome/build_gen/platformio.py +1 -3
  5. esphome/codegen.py +1 -0
  6. esphome/components/animation/animation.cpp +2 -2
  7. esphome/components/api/__init__.py +166 -3
  8. esphome/components/api/api_connection.cpp +84 -41
  9. esphome/components/api/api_connection.h +22 -16
  10. esphome/components/api/api_frame_helper.cpp +33 -19
  11. esphome/components/api/api_frame_helper.h +19 -4
  12. esphome/components/api/api_frame_helper_noise.cpp +41 -53
  13. esphome/components/api/api_frame_helper_noise.h +1 -1
  14. esphome/components/api/api_frame_helper_plaintext.cpp +22 -31
  15. esphome/components/api/api_frame_helper_plaintext.h +1 -1
  16. esphome/components/api/api_pb2.cpp +189 -15
  17. esphome/components/api/api_pb2.h +132 -20
  18. esphome/components/api/api_pb2_dump.cpp +97 -9
  19. esphome/components/api/api_pb2_service.cpp +118 -160
  20. esphome/components/api/api_pb2_service.h +31 -3
  21. esphome/components/api/api_server.cpp +68 -10
  22. esphome/components/api/api_server.h +32 -4
  23. esphome/components/api/custom_api_device.h +8 -8
  24. esphome/components/api/homeassistant_service.h +123 -6
  25. esphome/components/api/proto.h +6 -2
  26. esphome/components/api/user_services.h +2 -2
  27. esphome/components/as7341/sensor.py +1 -1
  28. esphome/components/audio/__init__.py +1 -1
  29. esphome/components/audio/audio.cpp +1 -1
  30. esphome/components/audio/audio_decoder.cpp +9 -9
  31. esphome/components/bl0906/bl0906.cpp +2 -2
  32. esphome/components/bl0942/bl0942.cpp +2 -2
  33. esphome/components/ble_client/__init__.py +1 -1
  34. esphome/components/bluetooth_proxy/__init__.py +4 -30
  35. esphome/components/bluetooth_proxy/bluetooth_connection.cpp +11 -4
  36. esphome/components/bluetooth_proxy/bluetooth_connection.h +2 -2
  37. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +2 -2
  38. esphome/components/camera_encoder/__init__.py +2 -4
  39. esphome/components/camera_encoder/esp32_camera_jpeg_encoder.cpp +4 -2
  40. esphome/components/camera_encoder/esp32_camera_jpeg_encoder.h +3 -1
  41. esphome/components/canbus/canbus.cpp +7 -5
  42. esphome/components/canbus/canbus.h +7 -7
  43. esphome/components/captive_portal/__init__.py +18 -1
  44. esphome/components/captive_portal/captive_portal.cpp +40 -46
  45. esphome/components/captive_portal/captive_portal.h +20 -22
  46. esphome/components/captive_portal/dns_server_esp32_idf.cpp +205 -0
  47. esphome/components/captive_portal/dns_server_esp32_idf.h +27 -0
  48. esphome/components/ccs811/ccs811.cpp +1 -1
  49. esphome/components/climate/climate.cpp +10 -7
  50. esphome/components/cm1106/cm1106.cpp +1 -1
  51. esphome/components/copy/lock/copy_lock.cpp +1 -1
  52. esphome/components/cover/cover.cpp +1 -0
  53. esphome/components/daikin_arc/daikin_arc.cpp +19 -12
  54. esphome/components/dashboard_import/dashboard_import.cpp +1 -1
  55. esphome/components/dashboard_import/dashboard_import.h +1 -1
  56. esphome/components/deep_sleep/__init__.py +9 -2
  57. esphome/components/deep_sleep/deep_sleep_component.h +11 -9
  58. esphome/components/deep_sleep/deep_sleep_esp32.cpp +51 -27
  59. esphome/components/ektf2232/touchscreen/__init__.py +8 -5
  60. esphome/components/ektf2232/touchscreen/ektf2232.cpp +4 -4
  61. esphome/components/ektf2232/touchscreen/ektf2232.h +2 -2
  62. esphome/components/epaper_spi/__init__.py +1 -0
  63. esphome/components/epaper_spi/display.py +80 -0
  64. esphome/components/epaper_spi/epaper_spi.cpp +227 -0
  65. esphome/components/epaper_spi/epaper_spi.h +93 -0
  66. esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.cpp +42 -0
  67. esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.h +45 -0
  68. esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp +135 -0
  69. esphome/components/epaper_spi/epaper_spi_spectra_e6.h +23 -0
  70. esphome/components/es7210/es7210.cpp +3 -3
  71. esphome/components/esp32/__init__.py +256 -340
  72. esphome/components/esp32/boards.py +81 -0
  73. esphome/components/esp32/preferences.cpp +23 -17
  74. esphome/components/esp32_ble/__init__.py +167 -44
  75. esphome/components/esp32_ble/ble.cpp +47 -3
  76. esphome/components/esp32_ble/ble.h +18 -0
  77. esphome/components/esp32_ble/ble_advertising.cpp +7 -3
  78. esphome/components/esp32_ble/ble_advertising.h +4 -0
  79. esphome/components/esp32_ble/ble_uuid.cpp +16 -42
  80. esphome/components/esp32_ble_beacon/__init__.py +3 -4
  81. esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +0 -4
  82. esphome/components/esp32_ble_client/ble_client_base.cpp +14 -12
  83. esphome/components/esp32_ble_server/__init__.py +28 -14
  84. esphome/components/esp32_ble_server/ble_characteristic.cpp +67 -57
  85. esphome/components/esp32_ble_server/ble_characteristic.h +27 -16
  86. esphome/components/esp32_ble_server/ble_descriptor.cpp +4 -3
  87. esphome/components/esp32_ble_server/ble_descriptor.h +13 -9
  88. esphome/components/esp32_ble_server/ble_server.cpp +59 -24
  89. esphome/components/esp32_ble_server/ble_server.h +38 -20
  90. esphome/components/esp32_ble_server/ble_server_automations.cpp +49 -33
  91. esphome/components/esp32_ble_server/ble_server_automations.h +39 -24
  92. esphome/components/esp32_ble_tracker/__init__.py +25 -80
  93. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +2 -8
  94. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +0 -3
  95. esphome/components/esp32_camera/__init__.py +1 -3
  96. esphome/components/esp32_can/esp32_can.cpp +22 -4
  97. esphome/components/esp32_can/esp32_can.h +3 -0
  98. esphome/components/esp32_hosted/__init__.py +2 -1
  99. esphome/components/esp32_improv/esp32_improv_component.cpp +135 -65
  100. esphome/components/esp32_improv/esp32_improv_component.h +7 -1
  101. esphome/components/esp32_rmt_led_strip/led_strip.cpp +1 -1
  102. esphome/components/esp8266/__init__.py +3 -3
  103. esphome/components/esphome/ota/__init__.py +21 -2
  104. esphome/components/esphome/ota/ota_esphome.cpp +456 -146
  105. esphome/components/esphome/ota/ota_esphome.h +49 -2
  106. esphome/components/ethernet/__init__.py +39 -22
  107. esphome/components/ethernet/ethernet_component.cpp +28 -5
  108. esphome/components/ethernet/ethernet_component.h +5 -1
  109. esphome/components/external_components/__init__.py +8 -6
  110. esphome/components/fingerprint_grow/fingerprint_grow.cpp +1 -1
  111. esphome/components/fingerprint_grow/fingerprint_grow.h +2 -1
  112. esphome/components/font/__init__.py +5 -5
  113. esphome/components/graph/graph.cpp +1 -1
  114. esphome/components/graphical_display_menu/graphical_display_menu.cpp +3 -2
  115. esphome/components/haier/hon_climate.cpp +2 -2
  116. esphome/components/haier/hon_climate.h +1 -1
  117. esphome/components/hdc1080/hdc1080.cpp +42 -34
  118. esphome/components/hdc1080/hdc1080.h +1 -3
  119. esphome/components/homeassistant/number/homeassistant_number.cpp +2 -2
  120. esphome/components/homeassistant/switch/homeassistant_switch.cpp +2 -2
  121. esphome/components/http_request/__init__.py +3 -3
  122. esphome/components/htu21d/htu21d.cpp +13 -18
  123. esphome/components/htu21d/htu21d.h +1 -1
  124. esphome/components/i2s_audio/__init__.py +1 -2
  125. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +1 -1
  126. esphome/components/ili9xxx/ili9xxx_display.cpp +2 -2
  127. esphome/components/improv_serial/improv_serial_component.cpp +12 -15
  128. esphome/components/improv_serial/improv_serial_component.h +6 -8
  129. esphome/components/json/json_util.cpp +42 -44
  130. esphome/components/json/json_util.h +57 -0
  131. esphome/components/kamstrup_kmp/kamstrup_kmp.cpp +2 -2
  132. esphome/components/key_collector/key_collector.h +4 -4
  133. esphome/components/libretiny/__init__.py +6 -6
  134. esphome/components/libretiny/preferences.cpp +23 -16
  135. esphome/components/light/light_call.cpp +98 -120
  136. esphome/components/light/light_call.h +17 -7
  137. esphome/components/lm75b/__init__.py +0 -0
  138. esphome/components/lm75b/lm75b.cpp +39 -0
  139. esphome/components/lm75b/lm75b.h +19 -0
  140. esphome/components/lm75b/sensor.py +34 -0
  141. esphome/components/lock/lock.h +12 -6
  142. esphome/components/logger/__init__.py +15 -27
  143. esphome/components/logger/logger.cpp +10 -20
  144. esphome/components/logger/logger.h +105 -62
  145. esphome/components/logger/logger_esp32.cpp +0 -48
  146. esphome/components/logger/logger_zephyr.cpp +2 -3
  147. esphome/components/logger/select/logger_level_select.cpp +6 -7
  148. esphome/components/logger/select/logger_level_select.h +7 -0
  149. esphome/components/ltr501/ltr501.cpp +7 -6
  150. esphome/components/ltr_als_ps/ltr_als_ps.cpp +7 -6
  151. esphome/components/matrix_keypad/matrix_keypad.h +4 -4
  152. esphome/components/max7219digit/max7219digit.cpp +1 -1
  153. esphome/components/mcp23xxx_base/mcp23xxx_base.h +3 -3
  154. esphome/components/mcp2515/mcp2515.cpp +31 -3
  155. esphome/components/mcp2515/mcp2515_defs.h +3 -1
  156. esphome/components/md5/md5.cpp +0 -26
  157. esphome/components/md5/md5.h +10 -20
  158. esphome/components/mdns/__init__.py +93 -19
  159. esphome/components/mdns/mdns_component.cpp +57 -94
  160. esphome/components/mdns/mdns_component.h +35 -11
  161. esphome/components/mdns/mdns_esp32.cpp +7 -13
  162. esphome/components/mdns/mdns_esp8266.cpp +7 -7
  163. esphome/components/mdns/mdns_libretiny.cpp +3 -4
  164. esphome/components/mdns/mdns_rp2040.cpp +3 -4
  165. esphome/components/mipi/__init__.py +1 -5
  166. esphome/components/mipi_spi/display.py +24 -8
  167. esphome/components/mipi_spi/mipi_spi.h +3 -3
  168. esphome/components/mixer/speaker/mixer_speaker.cpp +3 -3
  169. esphome/components/mmc5603/mmc5603.cpp +3 -3
  170. esphome/components/modbus/modbus.cpp +27 -13
  171. esphome/components/modbus/modbus.h +5 -3
  172. esphome/components/modbus/modbus_definitions.h +86 -0
  173. esphome/components/modbus_controller/__init__.py +29 -1
  174. esphome/components/modbus_controller/const.py +4 -0
  175. esphome/components/modbus_controller/modbus_controller.cpp +38 -13
  176. esphome/components/modbus_controller/modbus_controller.h +18 -29
  177. esphome/components/mpr121/mpr121.cpp +41 -42
  178. esphome/components/mpr121/mpr121.h +0 -1
  179. esphome/components/nau7802/nau7802.cpp +2 -2
  180. esphome/components/network/__init__.py +7 -3
  181. esphome/components/nextion/display.py +4 -4
  182. esphome/components/nextion/nextion.cpp +8 -8
  183. esphome/components/number/__init__.py +2 -0
  184. esphome/components/number/number_call.cpp +23 -12
  185. esphome/components/number/number_call.h +5 -0
  186. esphome/components/online_image/bmp_image.cpp +2 -1
  187. esphome/components/online_image/jpeg_image.cpp +4 -2
  188. esphome/components/opentherm/opentherm.cpp +5 -5
  189. esphome/components/opentherm/opentherm.h +3 -3
  190. esphome/components/openthread/openthread.cpp +11 -10
  191. esphome/components/openthread/openthread.h +0 -1
  192. esphome/components/ota/ota_backend.h +1 -0
  193. esphome/components/packages/__init__.py +10 -8
  194. esphome/components/packet_transport/packet_transport.cpp +2 -0
  195. esphome/components/pid/pid_controller.cpp +1 -1
  196. esphome/components/prometheus/prometheus_handler.cpp +239 -239
  197. esphome/components/psram/__init__.py +30 -28
  198. esphome/components/qmc5883l/qmc5883l.cpp +15 -0
  199. esphome/components/qmc5883l/qmc5883l.h +3 -0
  200. esphome/components/qmc5883l/sensor.py +31 -12
  201. esphome/components/remote_base/gobox_protocol.cpp +3 -3
  202. esphome/components/remote_receiver/__init__.py +14 -2
  203. esphome/components/remote_receiver/{remote_receiver_esp8266.cpp → remote_receiver.cpp} +2 -2
  204. esphome/components/remote_receiver/remote_receiver.h +4 -0
  205. esphome/components/remote_receiver/remote_receiver_esp32.cpp +18 -1
  206. esphome/components/remote_transmitter/__init__.py +2 -2
  207. esphome/components/remote_transmitter/remote_transmitter.cpp +103 -0
  208. esphome/components/rp2040/__init__.py +11 -11
  209. esphome/components/rtttl/rtttl.cpp +2 -2
  210. esphome/components/scd30/sensor.py +1 -1
  211. esphome/components/script/__init__.py +1 -1
  212. esphome/components/script/script.h +7 -7
  213. esphome/components/select/select.cpp +5 -4
  214. esphome/components/select/select_call.cpp +1 -1
  215. esphome/components/sensirion_common/i2c_sensirion.cpp +2 -1
  216. esphome/components/sensor/__init__.py +2 -0
  217. esphome/components/sha256/__init__.py +22 -0
  218. esphome/components/sha256/sha256.cpp +116 -0
  219. esphome/components/sha256/sha256.h +60 -0
  220. esphome/components/socket/lwip_raw_tcp_impl.cpp +34 -6
  221. esphome/components/sonoff_d1/sonoff_d1.cpp +1 -1
  222. esphome/components/spi/__init__.py +0 -3
  223. esphome/components/split_buffer/__init__.py +5 -0
  224. esphome/components/split_buffer/split_buffer.cpp +133 -0
  225. esphome/components/split_buffer/split_buffer.h +40 -0
  226. esphome/components/sps30/sps30.cpp +14 -10
  227. esphome/components/sps30/sps30.h +2 -0
  228. esphome/components/st7567_i2c/st7567_i2c.cpp +3 -1
  229. esphome/components/st7789v/st7789v.cpp +3 -2
  230. esphome/components/statsd/statsd.cpp +1 -1
  231. esphome/components/substitutions/__init__.py +3 -1
  232. esphome/components/substitutions/jinja.py +13 -3
  233. esphome/components/sx126x/__init__.py +16 -0
  234. esphome/components/sx126x/sx126x.cpp +15 -1
  235. esphome/components/sx126x/sx126x.h +9 -1
  236. esphome/components/sx126x/sx126x_reg.h +2 -0
  237. esphome/components/text_sensor/text_sensor.cpp +16 -0
  238. esphome/components/text_sensor/text_sensor.h +3 -10
  239. esphome/components/tormatic/tormatic_cover.cpp +1 -1
  240. esphome/components/tuya/select/tuya_select.cpp +1 -1
  241. esphome/components/tuya/tuya.cpp +29 -4
  242. esphome/components/uart/__init__.py +37 -27
  243. esphome/components/uart/uart.h +6 -0
  244. esphome/components/uart/uart_component.cpp +8 -0
  245. esphome/components/uart/uart_component.h +28 -0
  246. esphome/components/uart/uart_component_esp_idf.cpp +64 -10
  247. esphome/components/uart/uart_component_esp_idf.h +5 -2
  248. esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +1 -1
  249. esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp +1 -1
  250. esphome/components/uponor_smatrix/uponor_smatrix.cpp +3 -3
  251. esphome/components/usb_host/__init__.py +12 -2
  252. esphome/components/usb_host/usb_host.h +89 -14
  253. esphome/components/usb_host/usb_host_client.cpp +157 -22
  254. esphome/components/usb_host/usb_host_component.cpp +1 -1
  255. esphome/components/usb_uart/__init__.py +0 -1
  256. esphome/components/usb_uart/ch34x.cpp +4 -4
  257. esphome/components/usb_uart/cp210x.cpp +3 -3
  258. esphome/components/usb_uart/usb_uart.cpp +88 -32
  259. esphome/components/usb_uart/usb_uart.h +30 -6
  260. esphome/components/valve/valve.cpp +1 -0
  261. esphome/components/veml7700/veml7700.cpp +7 -6
  262. esphome/components/version/version_text_sensor.cpp +2 -1
  263. esphome/components/voice_assistant/voice_assistant.cpp +3 -2
  264. esphome/components/waveshare_epaper/waveshare_epaper.cpp +4 -4
  265. esphome/components/web_server/list_entities.cpp +3 -4
  266. esphome/components/web_server/list_entities.h +8 -10
  267. esphome/components/web_server/ota/__init__.py +1 -1
  268. esphome/components/web_server/ota/ota_web_server.cpp +9 -3
  269. esphome/components/web_server/web_server.cpp +509 -404
  270. esphome/components/web_server/web_server.h +5 -6
  271. esphome/components/web_server/web_server_v1.cpp +21 -19
  272. esphome/components/web_server_base/__init__.py +5 -2
  273. esphome/components/web_server_base/web_server_base.h +27 -7
  274. esphome/components/web_server_idf/__init__.py +1 -1
  275. esphome/components/web_server_idf/multipart.cpp +2 -2
  276. esphome/components/web_server_idf/multipart.h +2 -2
  277. esphome/components/web_server_idf/utils.cpp +2 -2
  278. esphome/components/web_server_idf/utils.h +2 -2
  279. esphome/components/web_server_idf/web_server_idf.cpp +118 -26
  280. esphome/components/web_server_idf/web_server_idf.h +12 -10
  281. esphome/components/wifi/__init__.py +13 -11
  282. esphome/components/wifi/wifi_component.cpp +74 -56
  283. esphome/components/wifi/wifi_component.h +4 -4
  284. esphome/components/wifi/wifi_component_esp8266.cpp +1 -1
  285. esphome/components/wifi/wifi_component_esp_idf.cpp +24 -4
  286. esphome/components/wireguard/__init__.py +1 -1
  287. esphome/components/wts01/__init__.py +0 -0
  288. esphome/components/wts01/sensor.py +41 -0
  289. esphome/components/wts01/wts01.cpp +91 -0
  290. esphome/components/wts01/wts01.h +27 -0
  291. esphome/components/zephyr/__init__.py +5 -5
  292. esphome/components/zwave_proxy/__init__.py +43 -0
  293. esphome/components/zwave_proxy/zwave_proxy.cpp +346 -0
  294. esphome/components/zwave_proxy/zwave_proxy.h +93 -0
  295. esphome/config.py +79 -24
  296. esphome/config_validation.py +13 -15
  297. esphome/const.py +9 -2
  298. esphome/core/__init__.py +33 -22
  299. esphome/core/component.cpp +28 -18
  300. esphome/core/component_iterator.h +2 -1
  301. esphome/core/config.py +15 -15
  302. esphome/core/defines.h +21 -0
  303. esphome/core/entity_helpers.py +9 -6
  304. esphome/core/hash_base.h +56 -0
  305. esphome/core/helpers.cpp +19 -3
  306. esphome/core/helpers.h +26 -0
  307. esphome/core/scheduler.cpp +5 -21
  308. esphome/core/scheduler.h +19 -8
  309. esphome/core/string_ref.h +1 -1
  310. esphome/core/time.cpp +5 -5
  311. esphome/cpp_generator.py +4 -29
  312. esphome/dashboard/const.py +21 -4
  313. esphome/dashboard/core.py +10 -8
  314. esphome/dashboard/dns.py +15 -0
  315. esphome/dashboard/entries.py +15 -21
  316. esphome/dashboard/models.py +76 -0
  317. esphome/dashboard/settings.py +7 -7
  318. esphome/dashboard/status/mdns.py +46 -2
  319. esphome/dashboard/web_server.py +367 -93
  320. esphome/espota2.py +112 -32
  321. esphome/external_files.py +6 -7
  322. esphome/git.py +8 -0
  323. esphome/helpers.py +124 -77
  324. esphome/loader.py +8 -9
  325. esphome/pins.py +2 -2
  326. esphome/platformio_api.py +56 -18
  327. esphome/storage_json.py +26 -21
  328. esphome/types.py +30 -2
  329. esphome/util.py +32 -16
  330. esphome/vscode.py +8 -8
  331. esphome/wizard.py +10 -10
  332. esphome/writer.py +50 -15
  333. esphome/yaml_util.py +37 -31
  334. esphome/zeroconf.py +12 -3
  335. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/METADATA +12 -12
  336. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/RECORD +340 -320
  337. esphome/components/event_emitter/__init__.py +0 -5
  338. esphome/components/event_emitter/event_emitter.cpp +0 -14
  339. esphome/components/event_emitter/event_emitter.h +0 -63
  340. esphome/components/remote_receiver/remote_receiver_libretiny.cpp +0 -125
  341. esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +0 -107
  342. esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp +0 -110
  343. esphome/components/uart/uart_component_esp32_arduino.cpp +0 -214
  344. esphome/components/uart/uart_component_esp32_arduino.h +0 -60
  345. esphome/components/wifi/wifi_component_esp32_arduino.cpp +0 -860
  346. esphome/core/string_ref.cpp +0 -12
  347. esphome/dashboard/util/file.py +0 -63
  348. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/WHEEL +0 -0
  349. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/entry_points.txt +0 -0
  350. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/licenses/LICENSE +0 -0
  351. {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/top_level.txt +0 -0
esphome/loader.py CHANGED
@@ -82,11 +82,10 @@ class ComponentManifest:
82
82
  return getattr(self.module, "CONFLICTS_WITH", [])
83
83
 
84
84
  @property
85
- def auto_load(self) -> list[str]:
86
- al = getattr(self.module, "AUTO_LOAD", [])
87
- if callable(al):
88
- return al()
89
- return al
85
+ def auto_load(
86
+ self,
87
+ ) -> list[str] | Callable[[], list[str]] | Callable[[ConfigType], list[str]]:
88
+ return getattr(self.module, "AUTO_LOAD", [])
90
89
 
91
90
  @property
92
91
  def codeowners(self) -> list[str]:
@@ -192,7 +191,7 @@ def install_custom_components_meta_finder():
192
191
  install_meta_finder(custom_components_dir)
193
192
 
194
193
 
195
- def _lookup_module(domain, exception):
194
+ def _lookup_module(domain: str, exception: bool) -> ComponentManifest | None:
196
195
  if domain in _COMPONENT_CACHE:
197
196
  return _COMPONENT_CACHE[domain]
198
197
 
@@ -219,16 +218,16 @@ def _lookup_module(domain, exception):
219
218
  return manif
220
219
 
221
220
 
222
- def get_component(domain, exception=False):
221
+ def get_component(domain: str, exception: bool = False) -> ComponentManifest | None:
223
222
  assert "." not in domain
224
223
  return _lookup_module(domain, exception)
225
224
 
226
225
 
227
- def get_platform(domain, platform):
226
+ def get_platform(domain: str, platform: str) -> ComponentManifest | None:
228
227
  full = f"{platform}.{domain}"
229
228
  return _lookup_module(full, False)
230
229
 
231
230
 
232
- _COMPONENT_CACHE = {}
231
+ _COMPONENT_CACHE: dict[str, ComponentManifest] = {}
233
232
  CORE_COMPONENTS_PATH = (Path(__file__).parent / "components").resolve()
234
233
  _COMPONENT_CACHE["esphome"] = ComponentManifest(esphome.core.config)
esphome/pins.py CHANGED
@@ -118,11 +118,11 @@ class PinRegistry(dict):
118
118
  parent_config = fconf.get_config_for_path(parent_path)
119
119
  final_val_fun(pin_config, parent_config)
120
120
  allow_others = pin_config.get(CONF_ALLOW_OTHER_USES, False)
121
- if count != 1 and not allow_others:
121
+ if count != 1 and not allow_others and not CORE.testing_mode:
122
122
  raise cv.Invalid(
123
123
  f"Pin {pin_config[CONF_NUMBER]} is used in multiple places"
124
124
  )
125
- if count == 1 and allow_others:
125
+ if count == 1 and allow_others and not CORE.testing_mode:
126
126
  raise cv.Invalid(
127
127
  f"Pin {pin_config[CONF_NUMBER]} incorrectly sets {CONF_ALLOW_OTHER_USES}: true"
128
128
  )
esphome/platformio_api.py CHANGED
@@ -5,6 +5,7 @@ import os
5
5
  from pathlib import Path
6
6
  import re
7
7
  import subprocess
8
+ from typing import Any
8
9
 
9
10
  from esphome.const import CONF_COMPILE_PROCESS_LIMIT, CONF_ESPHOME, KEY_CORE
10
11
  from esphome.core import CORE, EsphomeError
@@ -18,28 +19,59 @@ def patch_structhash():
18
19
  # removed/added. This might have unintended consequences, but this improves compile
19
20
  # times greatly when adding/removing components and a simple clean build solves
20
21
  # all issues
21
- from os import makedirs
22
- from os.path import getmtime, isdir, join
23
-
24
22
  from platformio.run import cli, helpers
25
23
 
26
24
  def patched_clean_build_dir(build_dir, *args):
27
25
  from platformio import fs
28
26
  from platformio.project.helpers import get_project_dir
29
27
 
30
- platformio_ini = join(get_project_dir(), "platformio.ini")
28
+ platformio_ini = Path(get_project_dir()) / "platformio.ini"
29
+
30
+ build_dir = Path(build_dir)
31
31
 
32
32
  # if project's config is modified
33
- if isdir(build_dir) and getmtime(platformio_ini) > getmtime(build_dir):
33
+ if (
34
+ build_dir.is_dir()
35
+ and platformio_ini.stat().st_mtime > build_dir.stat().st_mtime
36
+ ):
34
37
  fs.rmtree(build_dir)
35
38
 
36
- if not isdir(build_dir):
37
- makedirs(build_dir)
39
+ if not build_dir.is_dir():
40
+ build_dir.mkdir(parents=True)
38
41
 
39
42
  helpers.clean_build_dir = patched_clean_build_dir
40
43
  cli.clean_build_dir = patched_clean_build_dir
41
44
 
42
45
 
46
+ def patch_file_downloader():
47
+ """Patch PlatformIO's FileDownloader to retry on PackageException errors."""
48
+ from platformio.package.download import FileDownloader
49
+ from platformio.package.exception import PackageException
50
+
51
+ original_init = FileDownloader.__init__
52
+
53
+ def patched_init(self, *args: Any, **kwargs: Any) -> None:
54
+ max_retries = 3
55
+
56
+ for attempt in range(max_retries):
57
+ try:
58
+ return original_init(self, *args, **kwargs)
59
+ except PackageException as e:
60
+ if attempt < max_retries - 1:
61
+ _LOGGER.warning(
62
+ "Package download failed: %s. Retrying... (attempt %d/%d)",
63
+ str(e),
64
+ attempt + 1,
65
+ max_retries,
66
+ )
67
+ else:
68
+ # Final attempt - re-raise
69
+ raise
70
+ return None
71
+
72
+ FileDownloader.__init__ = patched_init
73
+
74
+
43
75
  IGNORE_LIB_WARNINGS = f"(?:{'|'.join(['Hash', 'Update'])})"
44
76
  FILTER_PLATFORMIO_LINES = [
45
77
  r"Verbose mode can be enabled via `-v, --verbose` option.*",
@@ -70,14 +102,19 @@ FILTER_PLATFORMIO_LINES = [
70
102
  r" - tool-esptool.* \(.*\)",
71
103
  r" - toolchain-.* \(.*\)",
72
104
  r"Creating BIN file .*",
105
+ r"Warning! Could not find file \".*.crt\"",
106
+ r"Warning! Arduino framework as an ESP-IDF component doesn't handle the `variant` field! The default `esp32` variant will be used.",
107
+ r"Warning: DEPRECATED: 'esptool.py' is deprecated. Please use 'esptool' instead. The '.py' suffix will be removed in a future major release.",
108
+ r"Warning: esp-idf-size exited with code 2",
109
+ r"esp_idf_size: error: unrecognized arguments: --ng",
73
110
  ]
74
111
 
75
112
 
76
113
  def run_platformio_cli(*args, **kwargs) -> str | int:
77
114
  os.environ["PLATFORMIO_FORCE_COLOR"] = "true"
78
- os.environ["PLATFORMIO_BUILD_DIR"] = os.path.abspath(CORE.relative_pioenvs_path())
115
+ os.environ["PLATFORMIO_BUILD_DIR"] = str(CORE.relative_pioenvs_path().absolute())
79
116
  os.environ.setdefault(
80
- "PLATFORMIO_LIBDEPS_DIR", os.path.abspath(CORE.relative_piolibdeps_path())
117
+ "PLATFORMIO_LIBDEPS_DIR", str(CORE.relative_piolibdeps_path().absolute())
81
118
  )
82
119
  # Suppress Python syntax warnings from third-party scripts during compilation
83
120
  os.environ.setdefault("PYTHONWARNINGS", "ignore::SyntaxWarning")
@@ -92,11 +129,12 @@ def run_platformio_cli(*args, **kwargs) -> str | int:
92
129
  import platformio.__main__
93
130
 
94
131
  patch_structhash()
132
+ patch_file_downloader()
95
133
  return run_external_command(platformio.__main__.main, *cmd, **kwargs)
96
134
 
97
135
 
98
136
  def run_platformio_cli_run(config, verbose, *args, **kwargs) -> str | int:
99
- command = ["run", "-d", CORE.build_path]
137
+ command = ["run", "-d", str(CORE.build_path)]
100
138
  if verbose:
101
139
  command += ["-v"]
102
140
  command += list(args)
@@ -128,8 +166,8 @@ def _run_idedata(config):
128
166
 
129
167
 
130
168
  def _load_idedata(config):
131
- platformio_ini = Path(CORE.relative_build_path("platformio.ini"))
132
- temp_idedata = Path(CORE.relative_internal_path("idedata", f"{CORE.name}.json"))
169
+ platformio_ini = CORE.relative_build_path("platformio.ini")
170
+ temp_idedata = CORE.relative_internal_path("idedata", f"{CORE.name}.json")
133
171
 
134
172
  changed = False
135
173
  if (
@@ -299,7 +337,7 @@ def process_stacktrace(config, line, backtrace_state):
299
337
 
300
338
  @dataclass
301
339
  class FlashImage:
302
- path: str
340
+ path: Path
303
341
  offset: str
304
342
 
305
343
 
@@ -308,17 +346,17 @@ class IDEData:
308
346
  self.raw = raw
309
347
 
310
348
  @property
311
- def firmware_elf_path(self):
312
- return self.raw["prog_path"]
349
+ def firmware_elf_path(self) -> Path:
350
+ return Path(self.raw["prog_path"])
313
351
 
314
352
  @property
315
- def firmware_bin_path(self) -> str:
316
- return str(Path(self.firmware_elf_path).with_suffix(".bin"))
353
+ def firmware_bin_path(self) -> Path:
354
+ return self.firmware_elf_path.with_suffix(".bin")
317
355
 
318
356
  @property
319
357
  def extra_flash_images(self) -> list[FlashImage]:
320
358
  return [
321
- FlashImage(path=entry["path"], offset=entry["offset"])
359
+ FlashImage(path=Path(entry["path"]), offset=entry["offset"])
322
360
  for entry in self.raw["extra"]["flash_images"]
323
361
  ]
324
362
 
esphome/storage_json.py CHANGED
@@ -1,11 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import binascii
4
- import codecs
5
4
  from datetime import datetime
6
5
  import json
7
6
  import logging
8
7
  import os
8
+ from pathlib import Path
9
9
 
10
10
  from esphome import const
11
11
  from esphome.const import CONF_DISABLED, CONF_MDNS
@@ -16,30 +16,35 @@ from esphome.types import CoreType
16
16
  _LOGGER = logging.getLogger(__name__)
17
17
 
18
18
 
19
- def storage_path() -> str:
20
- return os.path.join(CORE.data_dir, "storage", f"{CORE.config_filename}.json")
19
+ def storage_path() -> Path:
20
+ return CORE.data_dir / "storage" / f"{CORE.config_filename}.json"
21
21
 
22
22
 
23
- def ext_storage_path(config_filename: str) -> str:
24
- return os.path.join(CORE.data_dir, "storage", f"{config_filename}.json")
23
+ def ext_storage_path(config_filename: str) -> Path:
24
+ return CORE.data_dir / "storage" / f"{config_filename}.json"
25
25
 
26
26
 
27
- def esphome_storage_path() -> str:
28
- return os.path.join(CORE.data_dir, "esphome.json")
27
+ def esphome_storage_path() -> Path:
28
+ return CORE.data_dir / "esphome.json"
29
29
 
30
30
 
31
- def ignored_devices_storage_path() -> str:
32
- return os.path.join(CORE.data_dir, "ignored-devices.json")
31
+ def ignored_devices_storage_path() -> Path:
32
+ return CORE.data_dir / "ignored-devices.json"
33
33
 
34
34
 
35
- def trash_storage_path() -> str:
35
+ def trash_storage_path() -> Path:
36
36
  return CORE.relative_config_path("trash")
37
37
 
38
38
 
39
- def archive_storage_path() -> str:
39
+ def archive_storage_path() -> Path:
40
40
  return CORE.relative_config_path("archive")
41
41
 
42
42
 
43
+ def _to_path_if_not_none(value: str | None) -> Path | None:
44
+ """Convert a string to Path if it's not None."""
45
+ return Path(value) if value is not None else None
46
+
47
+
43
48
  class StorageJSON:
44
49
  def __init__(
45
50
  self,
@@ -52,8 +57,8 @@ class StorageJSON:
52
57
  address: str,
53
58
  web_port: int | None,
54
59
  target_platform: str,
55
- build_path: str | None,
56
- firmware_bin_path: str | None,
60
+ build_path: Path | None,
61
+ firmware_bin_path: Path | None,
57
62
  loaded_integrations: set[str],
58
63
  loaded_platforms: set[str],
59
64
  no_mdns: bool,
@@ -107,8 +112,8 @@ class StorageJSON:
107
112
  "address": self.address,
108
113
  "web_port": self.web_port,
109
114
  "esp_platform": self.target_platform,
110
- "build_path": self.build_path,
111
- "firmware_bin_path": self.firmware_bin_path,
115
+ "build_path": str(self.build_path),
116
+ "firmware_bin_path": str(self.firmware_bin_path),
112
117
  "loaded_integrations": sorted(self.loaded_integrations),
113
118
  "loaded_platforms": sorted(self.loaded_platforms),
114
119
  "no_mdns": self.no_mdns,
@@ -176,8 +181,8 @@ class StorageJSON:
176
181
  )
177
182
 
178
183
  @staticmethod
179
- def _load_impl(path: str) -> StorageJSON | None:
180
- with codecs.open(path, "r", encoding="utf-8") as f_handle:
184
+ def _load_impl(path: Path) -> StorageJSON | None:
185
+ with path.open("r", encoding="utf-8") as f_handle:
181
186
  storage = json.load(f_handle)
182
187
  storage_version = storage["storage_version"]
183
188
  name = storage.get("name")
@@ -190,8 +195,8 @@ class StorageJSON:
190
195
  address = storage.get("address")
191
196
  web_port = storage.get("web_port")
192
197
  esp_platform = storage.get("esp_platform")
193
- build_path = storage.get("build_path")
194
- firmware_bin_path = storage.get("firmware_bin_path")
198
+ build_path = _to_path_if_not_none(storage.get("build_path"))
199
+ firmware_bin_path = _to_path_if_not_none(storage.get("firmware_bin_path"))
195
200
  loaded_integrations = set(storage.get("loaded_integrations", []))
196
201
  loaded_platforms = set(storage.get("loaded_platforms", []))
197
202
  no_mdns = storage.get("no_mdns", False)
@@ -217,7 +222,7 @@ class StorageJSON:
217
222
  )
218
223
 
219
224
  @staticmethod
220
- def load(path: str) -> StorageJSON | None:
225
+ def load(path: Path) -> StorageJSON | None:
221
226
  try:
222
227
  return StorageJSON._load_impl(path)
223
228
  except Exception: # pylint: disable=broad-except
@@ -268,7 +273,7 @@ class EsphomeStorageJSON:
268
273
 
269
274
  @staticmethod
270
275
  def _load_impl(path: str) -> EsphomeStorageJSON | None:
271
- with codecs.open(path, "r", encoding="utf-8") as f_handle:
276
+ with Path(path).open("r", encoding="utf-8") as f_handle:
272
277
  storage = json.load(f_handle)
273
278
  storage_version = storage["storage_version"]
274
279
  cookie_secret = storage.get("cookie_secret")
esphome/types.py CHANGED
@@ -1,8 +1,10 @@
1
1
  """This helper module tracks commonly used types in the esphome python codebase."""
2
2
 
3
- from typing import TypedDict
3
+ import abc
4
+ from collections.abc import Sequence
5
+ from typing import Any, TypedDict
4
6
 
5
- from esphome.core import ID, EsphomeCore, Lambda
7
+ from esphome.core import ID, EsphomeCore, Lambda, TimePeriod
6
8
 
7
9
  ConfigFragmentType = (
8
10
  str
@@ -20,6 +22,32 @@ CoreType = EsphomeCore
20
22
  ConfigPathType = str | int
21
23
 
22
24
 
25
+ class Expression(abc.ABC):
26
+ __slots__ = ()
27
+
28
+ @abc.abstractmethod
29
+ def __str__(self):
30
+ """
31
+ Convert expression into C++ code
32
+ """
33
+
34
+
35
+ SafeExpType = (
36
+ Expression
37
+ | bool
38
+ | str
39
+ | int
40
+ | float
41
+ | TimePeriod
42
+ | type[bool]
43
+ | type[int]
44
+ | type[float]
45
+ | Sequence[Any]
46
+ )
47
+
48
+ TemplateArgsType = list[tuple[SafeExpType, str]]
49
+
50
+
23
51
  class EntityMetadata(TypedDict):
24
52
  """Metadata stored for each entity to help with duplicate detection."""
25
53
 
esphome/util.py CHANGED
@@ -1,20 +1,30 @@
1
1
  import collections
2
+ from collections.abc import Callable
2
3
  import io
3
4
  import logging
4
- import os
5
5
  from pathlib import Path
6
6
  import re
7
7
  import subprocess
8
8
  import sys
9
- from typing import Any
9
+ from typing import TYPE_CHECKING, Any
10
10
 
11
11
  from esphome import const
12
12
 
13
13
  _LOGGER = logging.getLogger(__name__)
14
14
 
15
+ if TYPE_CHECKING:
16
+ from esphome.config_validation import Schema
17
+ from esphome.cpp_generator import MockObjClass
18
+
15
19
 
16
20
  class RegistryEntry:
17
- def __init__(self, name, fun, type_id, schema):
21
+ def __init__(
22
+ self,
23
+ name: str,
24
+ fun: Callable[..., Any],
25
+ type_id: "MockObjClass",
26
+ schema: "Schema",
27
+ ):
18
28
  self.name = name
19
29
  self.fun = fun
20
30
  self.type_id = type_id
@@ -39,8 +49,8 @@ class Registry(dict[str, RegistryEntry]):
39
49
  self.base_schema = base_schema or {}
40
50
  self.type_id_key = type_id_key
41
51
 
42
- def register(self, name, type_id, schema):
43
- def decorator(fun):
52
+ def register(self, name: str, type_id: "MockObjClass", schema: "Schema"):
53
+ def decorator(fun: Callable[..., Any]):
44
54
  self[name] = RegistryEntry(name, fun, type_id, schema)
45
55
  return fun
46
56
 
@@ -48,8 +58,8 @@ class Registry(dict[str, RegistryEntry]):
48
58
 
49
59
 
50
60
  class SimpleRegistry(dict):
51
- def register(self, name, data):
52
- def decorator(fun):
61
+ def register(self, name: str, data: Any):
62
+ def decorator(fun: Callable[..., Any]):
53
63
  self[name] = (fun, data)
54
64
  return fun
55
65
 
@@ -86,7 +96,10 @@ def safe_input(prompt=""):
86
96
  return input()
87
97
 
88
98
 
89
- def shlex_quote(s):
99
+ def shlex_quote(s: str | Path) -> str:
100
+ # Convert Path objects to strings
101
+ if isinstance(s, Path):
102
+ s = str(s)
90
103
  if not s:
91
104
  return "''"
92
105
  if re.search(r"[^\w@%+=:,./-]", s) is None:
@@ -272,25 +285,28 @@ class OrderedDict(collections.OrderedDict):
272
285
  return dict(self).__repr__()
273
286
 
274
287
 
275
- def list_yaml_files(configs: list[str]) -> list[str]:
276
- files: list[str] = []
288
+ def list_yaml_files(configs: list[str | Path]) -> list[Path]:
289
+ files: list[Path] = []
277
290
  for config in configs:
278
- if os.path.isfile(config):
291
+ config = Path(config)
292
+ if not config.exists():
293
+ raise FileNotFoundError(f"Config path '{config}' does not exist!")
294
+ if config.is_file():
279
295
  files.append(config)
280
296
  else:
281
- files.extend(os.path.join(config, p) for p in os.listdir(config))
297
+ files.extend(config.glob("*"))
282
298
  files = filter_yaml_files(files)
283
299
  return sorted(files)
284
300
 
285
301
 
286
- def filter_yaml_files(files: list[str]) -> list[str]:
302
+ def filter_yaml_files(files: list[Path]) -> list[Path]:
287
303
  return [
288
304
  f
289
305
  for f in files
290
306
  if (
291
- os.path.splitext(f)[1] in (".yaml", ".yml")
292
- and os.path.basename(f) not in ("secrets.yaml", "secrets.yml")
293
- and not os.path.basename(f).startswith(".")
307
+ f.suffix in (".yaml", ".yml")
308
+ and f.name not in ("secrets.yaml", "secrets.yml")
309
+ and not f.name.startswith(".")
294
310
  )
295
311
  ]
296
312
 
esphome/vscode.py CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from io import StringIO
4
4
  import json
5
- import os
5
+ from pathlib import Path
6
6
  from typing import Any
7
7
 
8
8
  from esphome.config import Config, _format_vol_invalid, validate_config
@@ -67,24 +67,24 @@ def _read_file_content_from_json_on_stdin() -> str:
67
67
  return data["content"]
68
68
 
69
69
 
70
- def _print_file_read_event(path: str) -> None:
70
+ def _print_file_read_event(path: Path) -> None:
71
71
  """Print a file read event."""
72
72
  print(
73
73
  json.dumps(
74
74
  {
75
75
  "type": "read_file",
76
- "path": path,
76
+ "path": str(path),
77
77
  }
78
78
  )
79
79
  )
80
80
 
81
81
 
82
- def _request_and_get_stream_on_stdin(fname: str) -> StringIO:
82
+ def _request_and_get_stream_on_stdin(fname: Path) -> StringIO:
83
83
  _print_file_read_event(fname)
84
84
  return StringIO(_read_file_content_from_json_on_stdin())
85
85
 
86
86
 
87
- def _vscode_loader(fname: str) -> dict[str, Any]:
87
+ def _vscode_loader(fname: Path) -> dict[str, Any]:
88
88
  raw_yaml_stream = _request_and_get_stream_on_stdin(fname)
89
89
  # it is required to set the name on StringIO so document on start_mark
90
90
  # is set properly. Otherwise it is initialized with "<file>"
@@ -92,7 +92,7 @@ def _vscode_loader(fname: str) -> dict[str, Any]:
92
92
  return parse_yaml(fname, raw_yaml_stream, _vscode_loader)
93
93
 
94
94
 
95
- def _ace_loader(fname: str) -> dict[str, Any]:
95
+ def _ace_loader(fname: Path) -> dict[str, Any]:
96
96
  raw_yaml_stream = _request_and_get_stream_on_stdin(fname)
97
97
  return parse_yaml(fname, raw_yaml_stream)
98
98
 
@@ -120,10 +120,10 @@ def read_config(args):
120
120
  return
121
121
  CORE.vscode = True
122
122
  if args.ace: # Running from ESPHome Compiler dashboard, not vscode
123
- CORE.config_path = os.path.join(args.configuration, data["file"])
123
+ CORE.config_path = Path(args.configuration) / data["file"]
124
124
  loader = _ace_loader
125
125
  else:
126
- CORE.config_path = data["file"]
126
+ CORE.config_path = Path(data["file"])
127
127
  loader = _vscode_loader
128
128
 
129
129
  file_name = CORE.config_path
esphome/wizard.py CHANGED
@@ -1,4 +1,4 @@
1
- import os
1
+ from pathlib import Path
2
2
  import random
3
3
  import string
4
4
  from typing import Literal, NotRequired, TypedDict, Unpack
@@ -213,7 +213,7 @@ class WizardWriteKwargs(TypedDict):
213
213
  file_text: NotRequired[str]
214
214
 
215
215
 
216
- def wizard_write(path: str, **kwargs: Unpack[WizardWriteKwargs]) -> bool:
216
+ def wizard_write(path: Path, **kwargs: Unpack[WizardWriteKwargs]) -> bool:
217
217
  from esphome.components.bk72xx import boards as bk72xx_boards
218
218
  from esphome.components.esp32 import boards as esp32_boards
219
219
  from esphome.components.esp8266 import boards as esp8266_boards
@@ -256,13 +256,13 @@ def wizard_write(path: str, **kwargs: Unpack[WizardWriteKwargs]) -> bool:
256
256
  file_text = wizard_file(**kwargs)
257
257
 
258
258
  # Check if file already exists to prevent overwriting
259
- if os.path.exists(path) and os.path.isfile(path):
259
+ if path.exists() and path.is_file():
260
260
  safe_print(color(AnsiFore.RED, f'The file "{path}" already exists.'))
261
261
  return False
262
262
 
263
263
  write_file(path, file_text)
264
264
  storage = StorageJSON.from_wizard(name, name, f"{name}.local", hardware)
265
- storage_path = ext_storage_path(os.path.basename(path))
265
+ storage_path = ext_storage_path(path.name)
266
266
  storage.save(storage_path)
267
267
 
268
268
  return True
@@ -301,7 +301,7 @@ def strip_accents(value: str) -> str:
301
301
  )
302
302
 
303
303
 
304
- def wizard(path: str) -> int:
304
+ def wizard(path: Path) -> int:
305
305
  from esphome.components.bk72xx import boards as bk72xx_boards
306
306
  from esphome.components.esp32 import boards as esp32_boards
307
307
  from esphome.components.esp8266 import boards as esp8266_boards
@@ -309,14 +309,14 @@ def wizard(path: str) -> int:
309
309
  from esphome.components.rp2040 import boards as rp2040_boards
310
310
  from esphome.components.rtl87xx import boards as rtl87xx_boards
311
311
 
312
- if not path.endswith(".yaml") and not path.endswith(".yml"):
312
+ if path.suffix not in (".yaml", ".yml"):
313
313
  safe_print(
314
- f"Please make your configuration file {color(AnsiFore.CYAN, path)} have the extension .yaml or .yml"
314
+ f"Please make your configuration file {color(AnsiFore.CYAN, str(path))} have the extension .yaml or .yml"
315
315
  )
316
316
  return 1
317
- if os.path.exists(path):
317
+ if path.exists():
318
318
  safe_print(
319
- f"Uh oh, it seems like {color(AnsiFore.CYAN, path)} already exists, please delete that file first or chose another configuration file."
319
+ f"Uh oh, it seems like {color(AnsiFore.CYAN, str(path))} already exists, please delete that file first or chose another configuration file."
320
320
  )
321
321
  return 2
322
322
 
@@ -549,7 +549,7 @@ def wizard(path: str) -> int:
549
549
  safe_print()
550
550
  safe_print(
551
551
  color(AnsiFore.CYAN, "DONE! I've now written a new configuration file to ")
552
- + color(AnsiFore.BOLD_CYAN, path)
552
+ + color(AnsiFore.BOLD_CYAN, str(path))
553
553
  )
554
554
  safe_print()
555
555
  safe_print("Next steps:")