python-ember-mug 1.3.0b4__tar.gz → 1.3.0b6__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.
- {python_ember_mug-1.3.0b4 → python_ember_mug-1.3.0b6}/PKG-INFO +4 -5
- {python_ember_mug-1.3.0b4 → python_ember_mug-1.3.0b6}/README.md +1 -3
- {python_ember_mug-1.3.0b4 → python_ember_mug-1.3.0b6}/ember_mug/__init__.py +1 -1
- {python_ember_mug-1.3.0b4 → python_ember_mug-1.3.0b6}/ember_mug/cli/commands.py +2 -2
- {python_ember_mug-1.3.0b4 → python_ember_mug-1.3.0b6}/ember_mug/cli/helpers.py +6 -6
- {python_ember_mug-1.3.0b4 → python_ember_mug-1.3.0b6}/ember_mug/consts.py +11 -9
- {python_ember_mug-1.3.0b4 → python_ember_mug-1.3.0b6}/ember_mug/data.py +14 -4
- {python_ember_mug-1.3.0b4 → python_ember_mug-1.3.0b6}/ember_mug/formatting.py +5 -4
- {python_ember_mug-1.3.0b4 → python_ember_mug-1.3.0b6}/ember_mug/mug.py +9 -19
- {python_ember_mug-1.3.0b4 → python_ember_mug-1.3.0b6}/pyproject.toml +4 -3
- {python_ember_mug-1.3.0b4 → python_ember_mug-1.3.0b6}/.gitignore +0 -0
- {python_ember_mug-1.3.0b4 → python_ember_mug-1.3.0b6}/LICENSE +0 -0
- {python_ember_mug-1.3.0b4 → python_ember_mug-1.3.0b6}/ember_mug/__main__.py +0 -0
- {python_ember_mug-1.3.0b4 → python_ember_mug-1.3.0b6}/ember_mug/cli/__init__.py +0 -0
- {python_ember_mug-1.3.0b4 → python_ember_mug-1.3.0b6}/ember_mug/scanner.py +0 -0
- {python_ember_mug-1.3.0b4 → python_ember_mug-1.3.0b6}/ember_mug/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-ember-mug
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.0b6
|
|
4
4
|
Summary: Python Library for Ember Mugs.
|
|
5
5
|
Project-URL: Changelog, https://sopelj.github.io/python-ember-mug/changelog/
|
|
6
6
|
Project-URL: Documentation, https://sopelj.github.io/python-ember-mug/
|
|
@@ -16,9 +16,10 @@ Classifier: Programming Language :: Python :: 3
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
20
|
Requires-Python: >=3.11
|
|
20
21
|
Requires-Dist: bleak-retry-connector>=4.0.2
|
|
21
|
-
Requires-Dist: bleak
|
|
22
|
+
Requires-Dist: bleak<2.2.0,>=1.0.1
|
|
22
23
|
Provides-Extra: dev
|
|
23
24
|
Requires-Dist: ipython; extra == 'dev'
|
|
24
25
|
Provides-Extra: docs
|
|
@@ -44,7 +45,7 @@ Description-Content-Type: text/markdown
|
|
|
44
45
|
[](https://pypi.org/project/python-ember-mug/)
|
|
45
46
|
[](https://github.com/sopelj/python-ember-mug/actions/workflows/tests.yml)
|
|
46
47
|
[](https://codecov.io/gh/sopelj/python-ember-mug)
|
|
47
|
-

|
|
48
49
|
[](https://github.com/sopelj)
|
|
49
50
|
[](https://github.com/sopelj/python-ember-mug/blob/main/LICENSE)
|
|
50
51
|
[](https://github.com/pre-commit/pre-commit)
|
|
@@ -177,8 +178,6 @@ Device Data
|
|
|
177
178
|
+--------------+----------------------+
|
|
178
179
|
| Target Temp | 55.00°C |
|
|
179
180
|
+--------------+----------------------+
|
|
180
|
-
| Use Metric | True |
|
|
181
|
-
+--------------+----------------------+
|
|
182
181
|
|
|
183
182
|
Watching for changes
|
|
184
183
|
Current Temp changed from "24.50°C" to "25.50°"
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://pypi.org/project/python-ember-mug/)
|
|
5
5
|
[](https://github.com/sopelj/python-ember-mug/actions/workflows/tests.yml)
|
|
6
6
|
[](https://codecov.io/gh/sopelj/python-ember-mug)
|
|
7
|
-

|
|
8
8
|
[](https://github.com/sopelj)
|
|
9
9
|
[](https://github.com/sopelj/python-ember-mug/blob/main/LICENSE)
|
|
10
10
|
[](https://github.com/pre-commit/pre-commit)
|
|
@@ -137,8 +137,6 @@ Device Data
|
|
|
137
137
|
+--------------+----------------------+
|
|
138
138
|
| Target Temp | 55.00°C |
|
|
139
139
|
+--------------+----------------------+
|
|
140
|
-
| Use Metric | True |
|
|
141
|
-
+--------------+----------------------+
|
|
142
140
|
|
|
143
141
|
Watching for changes
|
|
144
142
|
Current Temp changed from "24.50°C" to "25.50°"
|
|
@@ -112,9 +112,9 @@ async def poll_device_cmd(args: Namespace) -> None:
|
|
|
112
112
|
for _ in CommandLoop():
|
|
113
113
|
for _ in range(60):
|
|
114
114
|
await asyncio.sleep(1)
|
|
115
|
-
print_changes(await mug.update_queued_attributes(), mug.data.
|
|
115
|
+
print_changes(await mug.update_queued_attributes(), mug.data.user_unit)
|
|
116
116
|
# Every minute do a full update
|
|
117
|
-
print_changes(await mug.update_all(), mug.data.
|
|
117
|
+
print_changes(await mug.update_all(), mug.data.user_unit)
|
|
118
118
|
|
|
119
119
|
|
|
120
120
|
async def get_device_value_cmd(args: Namespace) -> None:
|
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import re
|
|
6
5
|
from argparse import ArgumentTypeError
|
|
7
6
|
from collections import defaultdict
|
|
8
7
|
from functools import partial
|
|
9
8
|
from typing import TYPE_CHECKING
|
|
10
9
|
|
|
11
|
-
from ember_mug.consts import MAC_ADDRESS_REGEX
|
|
10
|
+
from ember_mug.consts import IS_MACOS, MAC_ADDRESS_REGEX, MAC_UUID_REGEX, TemperatureUnit
|
|
12
11
|
from ember_mug.data import Change
|
|
13
12
|
from ember_mug.formatting import format_led_colour, format_liquid_level, format_temp
|
|
14
13
|
|
|
@@ -26,7 +25,8 @@ base_formatters: dict[str, Callable] = {
|
|
|
26
25
|
|
|
27
26
|
def validate_mac(value: str) -> str:
|
|
28
27
|
"""Check if specified MAC Address is valid."""
|
|
29
|
-
|
|
28
|
+
mac_reg = MAC_UUID_REGEX if IS_MACOS else MAC_ADDRESS_REGEX
|
|
29
|
+
if not isinstance(value, str) or not mac_reg.match(value):
|
|
30
30
|
raise ArgumentTypeError("Invalid MAC Address")
|
|
31
31
|
return value.lower()
|
|
32
32
|
|
|
@@ -62,11 +62,11 @@ def print_info(mug: EmberMug) -> None:
|
|
|
62
62
|
print_table(list(mug.data.formatted.items()))
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
def print_changes(changes: list[Change],
|
|
65
|
+
def print_changes(changes: list[Change], unit: TemperatureUnit | None) -> None:
|
|
66
66
|
"""Print changes."""
|
|
67
67
|
formatters: dict[str, Callable] = {
|
|
68
|
-
"current_temp": partial(format_temp,
|
|
69
|
-
"target_temp": partial(format_temp,
|
|
68
|
+
"current_temp": partial(format_temp, unit=unit),
|
|
69
|
+
"target_temp": partial(format_temp, unit=unit),
|
|
70
70
|
**base_formatters,
|
|
71
71
|
}
|
|
72
72
|
for attr, old_value, new_value in changes:
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import platform
|
|
6
6
|
import re
|
|
7
|
-
from enum import
|
|
7
|
+
from enum import IntEnum, StrEnum
|
|
8
8
|
from functools import cached_property
|
|
9
9
|
from typing import NamedTuple
|
|
10
10
|
from uuid import UUID
|
|
@@ -17,7 +17,7 @@ EMBER_BLE_SIG = 0x03C1
|
|
|
17
17
|
DEFAULT_NAME = "Ember Device"
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
class DeviceType(
|
|
20
|
+
class DeviceType(StrEnum):
|
|
21
21
|
"""Base device types."""
|
|
22
22
|
|
|
23
23
|
CUP = "cup"
|
|
@@ -26,7 +26,7 @@ class DeviceType(str, Enum):
|
|
|
26
26
|
TUMBLER = "tumbler"
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
class DeviceModel(
|
|
29
|
+
class DeviceModel(StrEnum):
|
|
30
30
|
"""Know device models."""
|
|
31
31
|
|
|
32
32
|
CUP_6_OZ = "CM21S"
|
|
@@ -50,7 +50,7 @@ DEVICE_MODEL_NAMES: dict[DeviceModel, str] = {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
class DeviceColour(
|
|
53
|
+
class DeviceColour(StrEnum):
|
|
54
54
|
"""All colours possible found across models."""
|
|
55
55
|
|
|
56
56
|
SAGE_GREEN = "Sage Green"
|
|
@@ -66,7 +66,7 @@ class DeviceColour(str, Enum):
|
|
|
66
66
|
ROSE_GOLD = "Rose Gold"
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
class TemperatureUnit(
|
|
69
|
+
class TemperatureUnit(StrEnum):
|
|
70
70
|
"""Temperature Units."""
|
|
71
71
|
|
|
72
72
|
CELSIUS = "°C"
|
|
@@ -177,7 +177,7 @@ class LiquidState(IntEnum):
|
|
|
177
177
|
return self.label
|
|
178
178
|
|
|
179
179
|
|
|
180
|
-
class VolumeLevel(
|
|
180
|
+
class VolumeLevel(StrEnum):
|
|
181
181
|
"""Class to manage volume levels."""
|
|
182
182
|
|
|
183
183
|
LOW = "low"
|
|
@@ -240,7 +240,6 @@ ATTR_LABELS = {
|
|
|
240
240
|
"liquid_level": "Liquid Level",
|
|
241
241
|
"current_temp": "Current Temp",
|
|
242
242
|
"target_temp": "Target Temp",
|
|
243
|
-
"use_metric": "Use Metric",
|
|
244
243
|
"dsk": "DSK",
|
|
245
244
|
"udsk": "UDSK",
|
|
246
245
|
"date_time_zone": "Date Time + Time Zone",
|
|
@@ -269,8 +268,11 @@ UPDATE_ATTRS = {
|
|
|
269
268
|
EXTRA_ATTRS = {"battery_voltage", "date_time_zone", "udsk", "dsk"}
|
|
270
269
|
|
|
271
270
|
# Validation
|
|
272
|
-
|
|
271
|
+
# *Note*: Additional characters are escaped because Home Assistant uses LitElement with "v" mode which is stricter.
|
|
272
|
+
MUG_NAME_REGEX = re.compile(r"^[A-Za-z0-9,.\[\]#\(\)!\"\';:\|\-_+<>%= ]{1,16}$")
|
|
273
273
|
MUG_NAME_PATTERN = MUG_NAME_REGEX.pattern
|
|
274
|
-
MAC_ADDRESS_REGEX = re.compile(r"^([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$")
|
|
274
|
+
MAC_ADDRESS_REGEX = re.compile(r"^([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$", re.IGNORECASE)
|
|
275
|
+
MAC_UUID_REGEX = re.compile(r"^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$", re.IGNORECASE)
|
|
275
276
|
|
|
276
277
|
IS_LINUX = platform.system() == "Linux"
|
|
278
|
+
IS_MACOS = platform.system() == "Darwin"
|
|
@@ -211,7 +211,7 @@ class MugData(AsDict):
|
|
|
211
211
|
|
|
212
212
|
# Options
|
|
213
213
|
model_info: ModelInfo
|
|
214
|
-
use_metric: bool =
|
|
214
|
+
use_metric: bool | None = None
|
|
215
215
|
debug: bool = False
|
|
216
216
|
|
|
217
217
|
# Attributes
|
|
@@ -231,6 +231,15 @@ class MugData(AsDict):
|
|
|
231
231
|
date_time_zone: datetime | None = None
|
|
232
232
|
battery_voltage: int | None = None
|
|
233
233
|
|
|
234
|
+
@property
|
|
235
|
+
def user_unit(self) -> TemperatureUnit | None:
|
|
236
|
+
"""Get the correct unit for the user."""
|
|
237
|
+
if self.use_metric is False:
|
|
238
|
+
return TemperatureUnit.FAHRENHEIT
|
|
239
|
+
if self.use_metric is True:
|
|
240
|
+
return TemperatureUnit.CELSIUS
|
|
241
|
+
return None
|
|
242
|
+
|
|
234
243
|
@property
|
|
235
244
|
def meta_display(self) -> str:
|
|
236
245
|
"""Return Meta infor based on preference."""
|
|
@@ -263,12 +272,12 @@ class MugData(AsDict):
|
|
|
263
272
|
@property
|
|
264
273
|
def current_temp_display(self) -> str:
|
|
265
274
|
"""Human-readable current temp with unit."""
|
|
266
|
-
return format_temp(self.current_temp, self.
|
|
275
|
+
return format_temp(self.current_temp, self.user_unit)
|
|
267
276
|
|
|
268
277
|
@property
|
|
269
278
|
def target_temp_display(self) -> str:
|
|
270
279
|
"""Human-readable target temp with unit."""
|
|
271
|
-
return format_temp(self.target_temp, self.
|
|
280
|
+
return format_temp(self.target_temp, self.user_unit)
|
|
272
281
|
|
|
273
282
|
def update_info(self, **kwargs: Any) -> list[Change]:
|
|
274
283
|
"""Update attributes of the mug if they haven't changed."""
|
|
@@ -288,7 +297,7 @@ class MugData(AsDict):
|
|
|
288
297
|
@property
|
|
289
298
|
def formatted(self) -> dict[str, Any]:
|
|
290
299
|
"""Return human-readable names and values for all attributes for display."""
|
|
291
|
-
all_attrs = self.model_info.device_attributes
|
|
300
|
+
all_attrs = set(self.model_info.device_attributes)
|
|
292
301
|
if not self.debug:
|
|
293
302
|
all_attrs -= EXTRA_ATTRS
|
|
294
303
|
return {label: self.get_formatted_attr(attr) for attr, label in ATTR_LABELS.items() if attr in all_attrs}
|
|
@@ -296,6 +305,7 @@ class MugData(AsDict):
|
|
|
296
305
|
def as_dict(self) -> dict[str, Any]:
|
|
297
306
|
"""Dump all attributes as dict for info/debugging."""
|
|
298
307
|
data = asdict(self)
|
|
308
|
+
del data["use_metric"]
|
|
299
309
|
all_attrs = self.model_info.device_attributes
|
|
300
310
|
if not self.debug:
|
|
301
311
|
all_attrs -= EXTRA_ATTRS
|
|
@@ -5,16 +5,17 @@ from __future__ import annotations
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
|
+
from .consts import TemperatureUnit
|
|
8
9
|
from .data import Colour
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
def format_temp(temp: float,
|
|
12
|
+
def format_temp(temp: float, unit: TemperatureUnit | None = None) -> str:
|
|
12
13
|
"""Format temperature with the correct unit."""
|
|
13
|
-
|
|
14
|
-
return f"{
|
|
14
|
+
value = f"{temp:.2f}"
|
|
15
|
+
return f"{value}{unit.value}" if unit else value
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
def format_capacity(capacity: int | None, metric: bool =
|
|
18
|
+
def format_capacity(capacity: int | None, metric: bool | None = None) -> str:
|
|
18
19
|
"""Format capacity for display."""
|
|
19
20
|
if capacity is None:
|
|
20
21
|
return "Unknown"
|
|
@@ -7,7 +7,7 @@ import contextlib
|
|
|
7
7
|
import logging
|
|
8
8
|
import os
|
|
9
9
|
from datetime import UTC, datetime
|
|
10
|
-
from enum import
|
|
10
|
+
from enum import StrEnum
|
|
11
11
|
from functools import cached_property
|
|
12
12
|
from time import time
|
|
13
13
|
from typing import TYPE_CHECKING, Any, Concatenate, Literal, ParamSpec, TypeVar
|
|
@@ -46,7 +46,7 @@ if TYPE_CHECKING:
|
|
|
46
46
|
from bleak.backends.characteristic import BleakGATTCharacteristic
|
|
47
47
|
from bleak.backends.device import BLEDevice
|
|
48
48
|
|
|
49
|
-
TempUnitType = Literal["°C", "°F"] | TemperatureUnit |
|
|
49
|
+
TempUnitType = Literal["°C", "°F"] | TemperatureUnit | StrEnum
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
logger = logging.getLogger(__name__)
|
|
@@ -68,7 +68,7 @@ def require_attribute(
|
|
|
68
68
|
"""Inner decorator."""
|
|
69
69
|
|
|
70
70
|
async def wrapper(self: EmberMug, *args: P.args, **kwargs: P.kwargs) -> T:
|
|
71
|
-
if self.has_attribute(attr_name)
|
|
71
|
+
if not self.has_attribute(attr_name):
|
|
72
72
|
device_type = self.data.model_info.device_type.value
|
|
73
73
|
raise NotImplementedError(
|
|
74
74
|
f"The {device_type} does not have the {attr_name} attribute",
|
|
@@ -87,7 +87,7 @@ class EmberMug:
|
|
|
87
87
|
self,
|
|
88
88
|
ble_device: BLEDevice,
|
|
89
89
|
model_info: ModelInfo,
|
|
90
|
-
use_metric: bool =
|
|
90
|
+
use_metric: bool | None = None,
|
|
91
91
|
debug: bool = False,
|
|
92
92
|
**kwargs: Any,
|
|
93
93
|
) -> None:
|
|
@@ -136,17 +136,13 @@ class EmberMug:
|
|
|
136
136
|
|
|
137
137
|
def _convert_to_device_unit(self, value: float) -> float:
|
|
138
138
|
"""Convert user value to the unit the device expects."""
|
|
139
|
-
if self.data.
|
|
140
|
-
return convert_temp_to_fahrenheit(value)
|
|
141
|
-
if not self.data.use_metric and self.data.temperature_unit != TemperatureUnit.FAHRENHEIT:
|
|
139
|
+
if self.data.user_unit == TemperatureUnit.FAHRENHEIT:
|
|
142
140
|
return convert_temp_to_celsius(value)
|
|
143
141
|
return value
|
|
144
142
|
|
|
145
143
|
def _convert_to_user_unit(self, value: float) -> float:
|
|
146
144
|
"""Convert device value to the unit the user expects."""
|
|
147
|
-
if self.data.
|
|
148
|
-
return convert_temp_to_celsius(value)
|
|
149
|
-
if not self.data.use_metric and self.data.temperature_unit != TemperatureUnit.FAHRENHEIT:
|
|
145
|
+
if self.data.user_unit == TemperatureUnit.FAHRENHEIT:
|
|
150
146
|
return convert_temp_to_fahrenheit(value)
|
|
151
147
|
return value
|
|
152
148
|
|
|
@@ -291,7 +287,7 @@ class EmberMug:
|
|
|
291
287
|
|
|
292
288
|
async def pair(self) -> None:
|
|
293
289
|
"""Attempt to pair."""
|
|
294
|
-
with contextlib.suppress(BleakError, EOFError):
|
|
290
|
+
with contextlib.suppress(BleakError, EOFError, NotImplementedError):
|
|
295
291
|
await self._ensure_connection()
|
|
296
292
|
await self._client.pair()
|
|
297
293
|
|
|
@@ -314,7 +310,7 @@ class EmberMug:
|
|
|
314
310
|
|
|
315
311
|
async def set_target_temp(self, target_temp: float) -> None:
|
|
316
312
|
"""Set new target temp for mug."""
|
|
317
|
-
unit = TemperatureUnit.
|
|
313
|
+
unit = TemperatureUnit.FAHRENHEIT if self.data.use_metric is False else TemperatureUnit.CELSIUS
|
|
318
314
|
min_temp, max_temp = MIN_MAX_TEMPS[unit]
|
|
319
315
|
if target_temp != 0 and not (min_temp <= target_temp <= max_temp):
|
|
320
316
|
raise ValueError(f"Temperature should be between {min_temp} and {max_temp} or 0.")
|
|
@@ -405,17 +401,11 @@ class EmberMug:
|
|
|
405
401
|
|
|
406
402
|
async def set_temperature_unit(self, unit: TempUnitType) -> None:
|
|
407
403
|
"""Set mug unit."""
|
|
408
|
-
text_unit = unit.value if isinstance(unit,
|
|
404
|
+
text_unit = unit.value if isinstance(unit, StrEnum) else unit
|
|
409
405
|
unit_bytes = bytearray([1 if text_unit == TemperatureUnit.FAHRENHEIT else 0])
|
|
410
406
|
await self._write(MugCharacteristic.TEMPERATURE_UNIT, unit_bytes)
|
|
411
407
|
self.data.temperature_unit = TemperatureUnit(unit)
|
|
412
408
|
|
|
413
|
-
async def ensure_correct_unit(self) -> None:
|
|
414
|
-
"""Set mug unit if it's not what we want."""
|
|
415
|
-
desired = TemperatureUnit.CELSIUS if self.data.use_metric else TemperatureUnit.FAHRENHEIT
|
|
416
|
-
if self.data.temperature_unit != desired:
|
|
417
|
-
await self.set_temperature_unit(desired)
|
|
418
|
-
|
|
419
409
|
async def get_battery_voltage(self) -> int:
|
|
420
410
|
"""Get voltage and charge time."""
|
|
421
411
|
battery_voltage_bytes = await self._read(MugCharacteristic.CONTROL_REGISTER_DATA)
|
|
@@ -14,10 +14,11 @@ classifiers = [
|
|
|
14
14
|
'Programming Language :: Python :: 3.11',
|
|
15
15
|
'Programming Language :: Python :: 3.12',
|
|
16
16
|
'Programming Language :: Python :: 3.13',
|
|
17
|
+
'Programming Language :: Python :: 3.14',
|
|
17
18
|
]
|
|
18
19
|
dependencies = [
|
|
19
20
|
"bleak-retry-connector>=4.0.2",
|
|
20
|
-
"bleak>=1.0.1",
|
|
21
|
+
"bleak>=1.0.1,<2.2.0",
|
|
21
22
|
]
|
|
22
23
|
|
|
23
24
|
[project.optional-dependencies]
|
|
@@ -72,7 +73,7 @@ python = "3.13"
|
|
|
72
73
|
features = ["test"]
|
|
73
74
|
|
|
74
75
|
[[tool.hatch.envs.test.matrix]]
|
|
75
|
-
python = ["3.11", "3.12", "3.13"]
|
|
76
|
+
python = ["3.11", "3.12", "3.13", "3.14"]
|
|
76
77
|
|
|
77
78
|
[tool.hatch.envs.test.scripts]
|
|
78
79
|
cov = "pytest -vvv --asyncio-mode=auto --cov=ember_mug --cov-branch --cov-report=xml --cov-report=term-missing tests"
|
|
@@ -89,7 +90,7 @@ serve = "mkdocs serve --dev-addr localhost:8000"
|
|
|
89
90
|
[tool.black]
|
|
90
91
|
line-length = 120
|
|
91
92
|
skip-string-normalization = true
|
|
92
|
-
target-version = ["py311", "py312", "py313"]
|
|
93
|
+
target-version = ["py311", "py312", "py313", "py314"]
|
|
93
94
|
include = '\.pyi?$'
|
|
94
95
|
exclude = '''
|
|
95
96
|
/(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|