esphome 2025.2.2__py3-none-any.whl → 2025.3.0__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.
- esphome/__main__.py +9 -1
- esphome/components/api/api_connection.cpp +426 -70
- esphome/components/api/api_connection.h +117 -25
- esphome/components/api/api_pb2.cpp +33 -0
- esphome/components/api/api_pb2.h +4 -0
- esphome/components/api/api_server.cpp +2 -2
- esphome/components/api/list_entities.cpp +76 -22
- esphome/components/api/list_entities.h +1 -0
- esphome/components/api/subscribe_state.h +2 -0
- esphome/components/audio/__init__.py +1 -1
- esphome/components/audio/audio_decoder.cpp +43 -11
- esphome/components/audio/audio_reader.cpp +2 -2
- esphome/components/audio/audio_resampler.cpp +4 -2
- esphome/components/audio/audio_transfer_buffer.cpp +19 -9
- esphome/components/audio/audio_transfer_buffer.h +7 -2
- esphome/components/bluetooth_proxy/bluetooth_proxy.h +8 -0
- esphome/components/bmp085/bmp085.cpp +1 -1
- esphome/components/chsc6x/__init__.py +2 -0
- esphome/components/chsc6x/chsc6x_touchscreen.cpp +47 -0
- esphome/components/chsc6x/chsc6x_touchscreen.h +34 -0
- esphome/components/chsc6x/touchscreen.py +33 -0
- esphome/components/climate/__init__.py +0 -1
- esphome/components/cst816/binary_sensor/__init__.py +2 -25
- esphome/components/cst816/touchscreen/cst816_touchscreen.cpp +3 -14
- esphome/components/cst816/touchscreen/cst816_touchscreen.h +0 -4
- esphome/components/esp32_ble_beacon/__init__.py +3 -1
- esphome/components/esp8266/gpio.py +1 -2
- esphome/components/font/__init__.py +198 -215
- esphome/components/font/font.cpp +4 -4
- esphome/components/font/font.h +1 -0
- esphome/components/graph/graph.cpp +4 -0
- esphome/components/graph/graph.h +4 -0
- esphome/components/haier/climate.py +11 -10
- esphome/components/hbridge/switch/hbridge_switch.cpp +2 -2
- esphome/components/heatpumpir/climate.py +2 -1
- esphome/components/heatpumpir/heatpumpir.cpp +1 -0
- esphome/components/heatpumpir/heatpumpir.h +1 -0
- esphome/components/i2c/__init__.py +6 -6
- esphome/components/i2c/i2c_bus_esp_idf.cpp +6 -2
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +1 -1
- esphome/components/ili9xxx/display.py +1 -0
- esphome/components/ili9xxx/ili9xxx_display.h +5 -0
- esphome/components/ili9xxx/ili9xxx_init.h +59 -0
- esphome/components/ld2450/__init__.py +51 -0
- esphome/components/ld2450/binary_sensor.py +47 -0
- esphome/components/ld2450/button/__init__.py +45 -0
- esphome/components/ld2450/button/reset_button.cpp +9 -0
- esphome/components/ld2450/button/reset_button.h +18 -0
- esphome/components/ld2450/button/restart_button.cpp +9 -0
- esphome/components/ld2450/button/restart_button.h +18 -0
- esphome/components/ld2450/ld2450.cpp +876 -0
- esphome/components/ld2450/ld2450.h +234 -0
- esphome/components/ld2450/number/__init__.py +121 -0
- esphome/components/ld2450/number/presence_timeout_number.cpp +12 -0
- esphome/components/ld2450/number/presence_timeout_number.h +18 -0
- esphome/components/ld2450/number/zone_coordinate_number.cpp +14 -0
- esphome/components/ld2450/number/zone_coordinate_number.h +19 -0
- esphome/components/ld2450/select/__init__.py +56 -0
- esphome/components/ld2450/select/baud_rate_select.cpp +12 -0
- esphome/components/ld2450/select/baud_rate_select.h +18 -0
- esphome/components/ld2450/select/zone_type_select.cpp +12 -0
- esphome/components/ld2450/select/zone_type_select.h +18 -0
- esphome/components/ld2450/sensor.py +156 -0
- esphome/components/ld2450/switch/__init__.py +45 -0
- esphome/components/ld2450/switch/bluetooth_switch.cpp +12 -0
- esphome/components/ld2450/switch/bluetooth_switch.h +18 -0
- esphome/components/ld2450/switch/multi_target_switch.cpp +12 -0
- esphome/components/ld2450/switch/multi_target_switch.h +18 -0
- esphome/components/ld2450/text_sensor.py +62 -0
- esphome/components/lvgl/defines.py +0 -2
- esphome/components/lvgl/font.cpp +1 -1
- esphome/components/lvgl/lvgl_esphome.cpp +27 -19
- esphome/components/lvgl/widgets/img.py +1 -3
- esphome/components/mcp2515/mcp2515.cpp +1 -0
- esphome/components/mdns/__init__.py +1 -1
- esphome/components/mixer/speaker/mixer_speaker.cpp +6 -1
- esphome/components/mixer/speaker/mixer_speaker.h +2 -0
- esphome/components/mlx90393/sensor.py +53 -33
- esphome/components/mlx90393/sensor_mlx90393.cpp +4 -0
- esphome/components/mlx90393/sensor_mlx90393.h +8 -3
- esphome/components/mqtt/__init__.py +2 -2
- esphome/components/msa3xx/__init__.py +189 -0
- esphome/components/msa3xx/binary_sensor.py +40 -0
- esphome/components/msa3xx/msa3xx.cpp +417 -0
- esphome/components/msa3xx/msa3xx.h +311 -0
- esphome/components/msa3xx/sensor.py +42 -0
- esphome/components/msa3xx/text_sensor.py +38 -0
- esphome/components/nfc/binary_sensor/__init__.py +4 -4
- esphome/components/opentherm/binary_sensor/__init__.py +4 -4
- esphome/components/opentherm/generate.py +6 -6
- esphome/components/opentherm/sensor/__init__.py +5 -6
- esphome/components/packages/__init__.py +35 -11
- esphome/components/pn532/binary_sensor.py +4 -4
- esphome/components/rc522/binary_sensor.py +4 -4
- esphome/components/resampler/speaker/resampler_speaker.h +2 -0
- esphome/components/socket/bsd_sockets_impl.cpp +1 -0
- esphome/components/socket/lwip_sockets_impl.cpp +1 -0
- esphome/components/socket/socket.h +3 -1
- esphome/components/speaker/speaker.h +2 -2
- esphome/components/ssd1306_base/__init__.py +7 -7
- esphome/components/thermostat/climate.py +1 -1
- esphome/components/tmp1075/tmp1075.cpp +7 -11
- esphome/components/tmp1075/tmp1075.h +1 -2
- esphome/components/tormatic/__init__.py +1 -0
- esphome/components/tormatic/cover.py +47 -0
- esphome/components/tormatic/tormatic_cover.cpp +355 -0
- esphome/components/tormatic/tormatic_cover.h +60 -0
- esphome/components/tormatic/tormatic_protocol.h +211 -0
- esphome/components/touchscreen/binary_sensor/__init__.py +3 -0
- esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp +7 -1
- esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h +3 -1
- esphome/components/touchscreen/touchscreen.cpp +3 -4
- esphome/components/udp/udp_component.h +4 -1
- esphome/components/web_server/list_entities.cpp +70 -66
- esphome/components/web_server/list_entities.h +43 -22
- esphome/components/web_server/web_server.cpp +345 -68
- esphome/components/web_server/web_server.h +138 -6
- esphome/components/web_server_base/__init__.py +1 -1
- esphome/components/web_server_idf/__init__.py +2 -0
- esphome/components/web_server_idf/web_server_idf.cpp +177 -30
- esphome/components/web_server_idf/web_server_idf.h +53 -4
- esphome/config_validation.py +23 -125
- esphome/const.py +5 -1
- esphome/core/config.py +15 -6
- esphome/core/defines.h +1 -1
- esphome/core/helpers.h +24 -3
- esphome/core/time.cpp +1 -0
- esphome/cpp_generator.py +3 -3
- esphome/dashboard/core.py +30 -21
- esphome/dashboard/dns.py +7 -1
- esphome/dashboard/entries.py +83 -16
- esphome/dashboard/settings.py +0 -4
- esphome/dashboard/status/mdns.py +43 -14
- esphome/dashboard/status/mqtt.py +22 -9
- esphome/dashboard/status/ping.py +54 -10
- esphome/dashboard/web_server.py +56 -24
- esphome/storage_json.py +4 -0
- esphome/wizard.py +13 -17
- esphome/writer.py +1 -3
- esphome/yaml_util.py +36 -33
- esphome/zeroconf.py +9 -21
- {esphome-2025.2.2.dist-info → esphome-2025.3.0.dist-info}/METADATA +7 -7
- {esphome-2025.2.2.dist-info → esphome-2025.3.0.dist-info}/RECORD +147 -107
- esphome/components/cst816/binary_sensor/cst816_button.h +0 -27
- {esphome-2025.2.2.dist-info → esphome-2025.3.0.dist-info}/LICENSE +0 -0
- {esphome-2025.2.2.dist-info → esphome-2025.3.0.dist-info}/WHEEL +0 -0
- {esphome-2025.2.2.dist-info → esphome-2025.3.0.dist-info}/entry_points.txt +0 -0
- {esphome-2025.2.2.dist-info → esphome-2025.3.0.dist-info}/top_level.txt +0 -0
esphome/wizard.py
CHANGED
@@ -144,17 +144,17 @@ def wizard_file(**kwargs):
|
|
144
144
|
|
145
145
|
# Configure API
|
146
146
|
if "password" in kwargs:
|
147
|
-
config += f
|
147
|
+
config += f' password: "{kwargs["password"]}"\n'
|
148
148
|
if "api_encryption_key" in kwargs:
|
149
|
-
config += f
|
149
|
+
config += f' encryption:\n key: "{kwargs["api_encryption_key"]}"\n'
|
150
150
|
|
151
151
|
# Configure OTA
|
152
152
|
config += "\nota:\n"
|
153
153
|
config += " - platform: esphome\n"
|
154
154
|
if "ota_password" in kwargs:
|
155
|
-
config += f
|
155
|
+
config += f' password: "{kwargs["ota_password"]}"'
|
156
156
|
elif "password" in kwargs:
|
157
|
-
config += f
|
157
|
+
config += f' password: "{kwargs["password"]}"'
|
158
158
|
|
159
159
|
# Configuring wifi
|
160
160
|
config += "\n\nwifi:\n"
|
@@ -181,18 +181,14 @@ def wizard_file(**kwargs):
|
|
181
181
|
password: "{fallback_psk}"
|
182
182
|
|
183
183
|
captive_portal:
|
184
|
-
""".format(
|
185
|
-
**kwargs
|
186
|
-
)
|
184
|
+
""".format(**kwargs)
|
187
185
|
else:
|
188
186
|
config += """
|
189
187
|
# Enable fallback hotspot in case wifi connection fails
|
190
188
|
ap:
|
191
189
|
ssid: "{fallback_name}"
|
192
190
|
password: "{fallback_psk}"
|
193
|
-
""".format(
|
194
|
-
**kwargs
|
195
|
-
)
|
191
|
+
""".format(**kwargs)
|
196
192
|
|
197
193
|
return config
|
198
194
|
|
@@ -388,19 +384,19 @@ def wizard(path):
|
|
388
384
|
safe_print()
|
389
385
|
# Don't sleep because user needs to copy link
|
390
386
|
if platform == "ESP32":
|
391
|
-
safe_print(f
|
387
|
+
safe_print(f'For example "{color(Fore.BOLD_WHITE, "nodemcu-32s")}".')
|
392
388
|
boards_list = esp32_boards.BOARDS.items()
|
393
389
|
elif platform == "ESP8266":
|
394
|
-
safe_print(f
|
390
|
+
safe_print(f'For example "{color(Fore.BOLD_WHITE, "nodemcuv2")}".')
|
395
391
|
boards_list = esp8266_boards.BOARDS.items()
|
396
392
|
elif platform == "BK72XX":
|
397
|
-
safe_print(f
|
393
|
+
safe_print(f'For example "{color(Fore.BOLD_WHITE, "cb2s")}".')
|
398
394
|
boards_list = bk72xx_boards.BOARDS.items()
|
399
395
|
elif platform == "RTL87XX":
|
400
|
-
safe_print(f
|
396
|
+
safe_print(f'For example "{color(Fore.BOLD_WHITE, "wr3")}".')
|
401
397
|
boards_list = rtl87xx_boards.BOARDS.items()
|
402
398
|
elif platform == "RP2040":
|
403
|
-
safe_print(f
|
399
|
+
safe_print(f'For example "{color(Fore.BOLD_WHITE, "rpipicow")}".')
|
404
400
|
boards_list = rp2040_boards.BOARDS.items()
|
405
401
|
|
406
402
|
else:
|
@@ -439,7 +435,7 @@ def wizard(path):
|
|
439
435
|
f"First, what's the {color(Fore.GREEN, 'SSID')} (the name) of the WiFi network {name} should connect to?"
|
440
436
|
)
|
441
437
|
sleep(1.5)
|
442
|
-
safe_print(f
|
438
|
+
safe_print(f'For example "{color(Fore.BOLD_WHITE, "Abraham Linksys")}".')
|
443
439
|
while True:
|
444
440
|
ssid = safe_input(color(Fore.BOLD_WHITE, "(ssid): "))
|
445
441
|
try:
|
@@ -465,7 +461,7 @@ def wizard(path):
|
|
465
461
|
f"Now please state the {color(Fore.GREEN, 'password')} of the WiFi network so that I can connect to it (Leave empty for no password)"
|
466
462
|
)
|
467
463
|
safe_print()
|
468
|
-
safe_print(f
|
464
|
+
safe_print(f'For example "{color(Fore.BOLD_WHITE, "PASSWORD42")}"')
|
469
465
|
sleep(0.5)
|
470
466
|
psk = safe_input(color(Fore.BOLD_WHITE, "(PSK): "))
|
471
467
|
safe_print(
|
esphome/writer.py
CHANGED
esphome/yaml_util.py
CHANGED
@@ -273,48 +273,18 @@ class ESPHomeLoaderMixin:
|
|
273
273
|
|
274
274
|
@_add_data_ref
|
275
275
|
def construct_include(self, node):
|
276
|
+
from esphome.const import CONF_VARS
|
277
|
+
|
276
278
|
def extract_file_vars(node):
|
277
279
|
fields = self.construct_yaml_map(node)
|
278
280
|
file = fields.get("file")
|
279
281
|
if file is None:
|
280
282
|
raise yaml.MarkedYAMLError("Must include 'file'", node.start_mark)
|
281
|
-
vars = fields.get(
|
283
|
+
vars = fields.get(CONF_VARS)
|
282
284
|
if vars:
|
283
285
|
vars = {k: str(v) for k, v in vars.items()}
|
284
286
|
return file, vars
|
285
287
|
|
286
|
-
def substitute_vars(config, vars):
|
287
|
-
from esphome.components import substitutions
|
288
|
-
from esphome.const import CONF_DEFAULTS, CONF_SUBSTITUTIONS
|
289
|
-
|
290
|
-
org_subs = None
|
291
|
-
result = config
|
292
|
-
if not isinstance(config, dict):
|
293
|
-
# when the included yaml contains a list or a scalar
|
294
|
-
# wrap it into an OrderedDict because do_substitution_pass expects it
|
295
|
-
result = OrderedDict([("yaml", config)])
|
296
|
-
elif CONF_SUBSTITUTIONS in result:
|
297
|
-
org_subs = result.pop(CONF_SUBSTITUTIONS)
|
298
|
-
|
299
|
-
defaults = {}
|
300
|
-
if CONF_DEFAULTS in result:
|
301
|
-
defaults = result.pop(CONF_DEFAULTS)
|
302
|
-
|
303
|
-
result[CONF_SUBSTITUTIONS] = vars
|
304
|
-
for k, v in defaults.items():
|
305
|
-
if k not in result[CONF_SUBSTITUTIONS]:
|
306
|
-
result[CONF_SUBSTITUTIONS][k] = v
|
307
|
-
|
308
|
-
# Ignore missing vars that refer to the top level substitutions
|
309
|
-
substitutions.do_substitution_pass(result, None, ignore_missing=True)
|
310
|
-
result.pop(CONF_SUBSTITUTIONS)
|
311
|
-
|
312
|
-
if not isinstance(config, dict):
|
313
|
-
result = result["yaml"] # unwrap the result
|
314
|
-
elif org_subs:
|
315
|
-
result[CONF_SUBSTITUTIONS] = org_subs
|
316
|
-
return result
|
317
|
-
|
318
288
|
if isinstance(node, yaml.nodes.MappingNode):
|
319
289
|
file, vars = extract_file_vars(node)
|
320
290
|
else:
|
@@ -432,6 +402,39 @@ def parse_yaml(file_name: str, file_handle: TextIOWrapper) -> Any:
|
|
432
402
|
)
|
433
403
|
|
434
404
|
|
405
|
+
def substitute_vars(config, vars):
|
406
|
+
from esphome.components import substitutions
|
407
|
+
from esphome.const import CONF_DEFAULTS, CONF_SUBSTITUTIONS
|
408
|
+
|
409
|
+
org_subs = None
|
410
|
+
result = config
|
411
|
+
if not isinstance(config, dict):
|
412
|
+
# when the included yaml contains a list or a scalar
|
413
|
+
# wrap it into an OrderedDict because do_substitution_pass expects it
|
414
|
+
result = OrderedDict([("yaml", config)])
|
415
|
+
elif CONF_SUBSTITUTIONS in result:
|
416
|
+
org_subs = result.pop(CONF_SUBSTITUTIONS)
|
417
|
+
|
418
|
+
defaults = {}
|
419
|
+
if CONF_DEFAULTS in result:
|
420
|
+
defaults = result.pop(CONF_DEFAULTS)
|
421
|
+
|
422
|
+
result[CONF_SUBSTITUTIONS] = vars
|
423
|
+
for k, v in defaults.items():
|
424
|
+
if k not in result[CONF_SUBSTITUTIONS]:
|
425
|
+
result[CONF_SUBSTITUTIONS][k] = v
|
426
|
+
|
427
|
+
# Ignore missing vars that refer to the top level substitutions
|
428
|
+
substitutions.do_substitution_pass(result, None, ignore_missing=True)
|
429
|
+
result.pop(CONF_SUBSTITUTIONS)
|
430
|
+
|
431
|
+
if not isinstance(config, dict):
|
432
|
+
result = result["yaml"] # unwrap the result
|
433
|
+
elif org_subs:
|
434
|
+
result[CONF_SUBSTITUTIONS] = org_subs
|
435
|
+
return result
|
436
|
+
|
437
|
+
|
435
438
|
def _load_yaml_internal(fname: str) -> Any:
|
436
439
|
"""Load a YAML file."""
|
437
440
|
try:
|
esphome/zeroconf.py
CHANGED
@@ -5,7 +5,13 @@ from dataclasses import dataclass
|
|
5
5
|
import logging
|
6
6
|
from typing import Callable
|
7
7
|
|
8
|
-
from zeroconf import
|
8
|
+
from zeroconf import (
|
9
|
+
AddressResolver,
|
10
|
+
IPVersion,
|
11
|
+
ServiceInfo,
|
12
|
+
ServiceStateChange,
|
13
|
+
Zeroconf,
|
14
|
+
)
|
9
15
|
from zeroconf.asyncio import AsyncServiceBrowser, AsyncServiceInfo, AsyncZeroconf
|
10
16
|
|
11
17
|
from esphome.storage_json import StorageJSON, ext_storage_path
|
@@ -16,15 +22,6 @@ _LOGGER = logging.getLogger(__name__)
|
|
16
22
|
_BACKGROUND_TASKS: set[asyncio.Task] = set()
|
17
23
|
|
18
24
|
|
19
|
-
class HostResolver(ServiceInfo):
|
20
|
-
"""Resolve a host name to an IP address."""
|
21
|
-
|
22
|
-
@property
|
23
|
-
def _is_complete(self) -> bool:
|
24
|
-
"""The ServiceInfo has all expected properties."""
|
25
|
-
return bool(self._ipv4_addresses)
|
26
|
-
|
27
|
-
|
28
25
|
class DashboardStatus:
|
29
26
|
def __init__(self, on_update: Callable[[dict[str, bool | None], []]]) -> None:
|
30
27
|
"""Initialize the dashboard status."""
|
@@ -166,19 +163,10 @@ class DashboardImportDiscovery:
|
|
166
163
|
)
|
167
164
|
|
168
165
|
|
169
|
-
def _make_host_resolver(host: str) -> HostResolver:
|
170
|
-
"""Create a new HostResolver for the given host name."""
|
171
|
-
name = host.partition(".")[0]
|
172
|
-
info = HostResolver(
|
173
|
-
ESPHOME_SERVICE_TYPE, f"{name}.{ESPHOME_SERVICE_TYPE}", server=f"{name}.local."
|
174
|
-
)
|
175
|
-
return info
|
176
|
-
|
177
|
-
|
178
166
|
class EsphomeZeroconf(Zeroconf):
|
179
167
|
def resolve_host(self, host: str, timeout: float = 3.0) -> list[str] | None:
|
180
168
|
"""Resolve a host name to an IP address."""
|
181
|
-
info =
|
169
|
+
info = AddressResolver(f"{host.partition('.')[0]}.local.")
|
182
170
|
if (
|
183
171
|
info.load_from_cache(self)
|
184
172
|
or (timeout and info.request(self, timeout * 1000))
|
@@ -192,7 +180,7 @@ class AsyncEsphomeZeroconf(AsyncZeroconf):
|
|
192
180
|
self, host: str, timeout: float = 3.0
|
193
181
|
) -> list[str] | None:
|
194
182
|
"""Resolve a host name to an IP address."""
|
195
|
-
info =
|
183
|
+
info = AddressResolver(f"{host.partition('.')[0]}.local.")
|
196
184
|
if (
|
197
185
|
info.load_from_cache(self.zeroconf)
|
198
186
|
or (timeout and await info.async_request(self.zeroconf, timeout * 1000))
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: esphome
|
3
|
-
Version: 2025.
|
3
|
+
Version: 2025.3.0
|
4
4
|
Summary: ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems.
|
5
5
|
Author-email: The ESPHome Authors <esphome@nabucasa.com>
|
6
6
|
License: MIT
|
@@ -23,22 +23,22 @@ Classifier: Topic :: Home Automation
|
|
23
23
|
Requires-Python: >=3.9.0
|
24
24
|
Description-Content-Type: text/markdown
|
25
25
|
License-File: LICENSE
|
26
|
-
Requires-Dist: cryptography ==
|
26
|
+
Requires-Dist: cryptography ==44.0.2
|
27
27
|
Requires-Dist: voluptuous ==0.14.2
|
28
28
|
Requires-Dist: PyYAML ==6.0.2
|
29
29
|
Requires-Dist: paho-mqtt ==1.6.1
|
30
30
|
Requires-Dist: colorama ==0.4.6
|
31
31
|
Requires-Dist: icmplib ==3.0.4
|
32
|
-
Requires-Dist: tornado ==6.4
|
32
|
+
Requires-Dist: tornado ==6.4.2
|
33
33
|
Requires-Dist: tzlocal ==5.2
|
34
34
|
Requires-Dist: tzdata >=2021.1
|
35
35
|
Requires-Dist: pyserial ==3.5
|
36
36
|
Requires-Dist: platformio ==6.1.16
|
37
|
-
Requires-Dist: esptool ==4.
|
37
|
+
Requires-Dist: esptool ==4.8.1
|
38
38
|
Requires-Dist: click ==8.1.7
|
39
39
|
Requires-Dist: esphome-dashboard ==20250212.0
|
40
|
-
Requires-Dist: aioesphomeapi ==29.
|
41
|
-
Requires-Dist: zeroconf ==0.
|
40
|
+
Requires-Dist: aioesphomeapi ==29.6.0
|
41
|
+
Requires-Dist: zeroconf ==0.146.1
|
42
42
|
Requires-Dist: puremagic ==1.27
|
43
43
|
Requires-Dist: ruamel.yaml ==0.18.6
|
44
44
|
Requires-Dist: esphome-glyphsets ==0.1.0
|
@@ -57,7 +57,7 @@ Requires-Dist: cairosvg ==2.7.1 ; extra == 'displays'
|
|
57
57
|
Provides-Extra: test
|
58
58
|
Requires-Dist: pylint ==3.2.7 ; extra == 'test'
|
59
59
|
Requires-Dist: flake8 ==7.0.0 ; extra == 'test'
|
60
|
-
Requires-Dist:
|
60
|
+
Requires-Dist: ruff ==0.9.2 ; extra == 'test'
|
61
61
|
Requires-Dist: pyupgrade ==3.15.2 ; extra == 'test'
|
62
62
|
Requires-Dist: pre-commit ; extra == 'test'
|
63
63
|
Requires-Dist: pytest ==8.2.0 ; extra == 'test'
|