Skip to content

Commit e9af217

Browse files
authored
Release 1.5.0 (#90)
2 parents 5d32b5e + f5b900f commit e9af217

File tree

12 files changed

+201
-124
lines changed

12 files changed

+201
-124
lines changed

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
## Changelog
22

3+
## v1.5.0
4+
5+
## ✨ Nové funkce
6+
7+
**Mark messages as read, improve logging (#88) @schizza**
8+
- přidán API point k podepsání známek `message_mark_as_read`
9+
- přidána možnost podepsat známku / známky
10+
- automaticky je známka podepsána na serveru školy, pokud se zavolá `service_call makr_seen`
11+
12+
## 🐛 Opravy chyb
13+
- opravena chyba duplicit v loggeru
14+
- `log format` nyní ukazuje i volající funkci
15+
16+
## 🧹 Refaktoring / Údržba
17+
18+
**Refactors Bakalari client handling (#89) @schizza**
19+
- centralizace `BakalariClient` na úroveň `async_setup_entry`
20+
- vytvoření jedné sdílené instance `BakalariClient`, aby nedocházelo k duplicitnímu vytváření instance u každého dítěte.
21+
- `BakalariClient` je sdílený pro všechny koordinatory
22+
323
## v1.4.1
424

525
## 🐛 Opravy chyb

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Custom komponenta pro Home Assistant, založená na [async-bakalari-api3](https:
4242
- zobrazení poslední přijaté známky nadále funguje bez rozdílu
4343
- přidána možnost `fire_event` pro vyvolání události při nové známce, bude sloužit k oznámení např. v mobilní aplikaci
4444
- přidána možnost Websocketu
45-
- další funkcionality v následujících verzích
45+
- známky lze nyní podepsat zavoláním `service_call` - `mark_as_seen` nebo `sign_all_marks`
4646

4747
Příklad metadat v senzoru `Všechny známky`
4848

bump-version.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tool.bumpversion]
2-
current_version = "1.4.1"
2+
current_version = "1.5.0"
33
commit = false
44
tag = false
55

custom_components/bakalari/__init__.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from homeassistant.helpers import device_registry as dr
1515
import voluptuous as vol
1616

17+
from .api import BakalariClient
1718
from .children import ChildrenIndex
1819
from .const import DOMAIN, MANUFACTURER, MODEL, PLATFORMS, SW_VERSION
1920
from .coordinator_marks import BakalariMarksCoordinator
@@ -41,7 +42,7 @@ class CustomFormatter(logging.Formatter):
4142
cyan = "\u001b[36m"
4243
white = "\u001b[37m"
4344

44-
_format = f"%(asctime)s - %(levelname)s [%(name)s]\n{reset}Message: %(message)s)"
45+
_format = f"%(asctime)s - %(levelname)s [%(name)s] %(funcName)s{reset}\nMessage: %(message)s)"
4546
dateformat = "%d/%m/%Y %H:%M:%S"
4647

4748
FORMATS = {
@@ -62,6 +63,10 @@ def format(self, record):
6263

6364
async def async_setup(hass: HomeAssistant, config) -> bool:
6465
"""Set up the Bakalari component."""
66+
67+
_dev_console_handler_for(
68+
logging.getLogger("custom_components.bakalari"), CustomFormatter()
69+
)
6570
hass.data.setdefault(DOMAIN, {})
6671
return True
6772

@@ -73,11 +78,19 @@ def _dev_console_handler_for(
7378
# pokud už nějaké handlery existují (na loggeru NEBO u předků), nedělej nic
7479
# if logger.hasHandlers():
7580
# return
81+
existing = [h for h in logger.handlers if getattr(h, "_bakalari_dev", False)]
82+
if existing:
83+
if formatter is not None:
84+
existing[0].setFormatter(formatter)
85+
logger.propagate = False
86+
configure_logging(logger.getEffectiveLevel())
87+
return
7688

7789
h = logging.StreamHandler() # default: stderr
7890
if formatter is None:
7991
formatter = logging.Formatter("[%(levelname)s] %(name)s: %(message)s")
8092
h.setFormatter(formatter)
93+
h._bakalari_dev = True # pyright: ignore[reportAttributeAccessIssue]
8194

8295
# level nech na loggeru; handler level nenech NOTSET explicitně – zbytečné
8396
logger.addHandler(h)
@@ -120,7 +133,7 @@ async def _srv_mark_message_seen(call) -> None:
120133
await coord.async_mark_message_seen(
121134
call.data["message_id"], call.data.get("child_key")
122135
)
123-
await coord.async_request_refresh()
136+
await coord.async_refresh()
124137

125138
async def _srv_refresh_messages(call) -> None: # noqa: ARG001
126139
await hass.data[DOMAIN][entry.entry_id]["messages"].async_refresh()
@@ -207,19 +220,32 @@ async def ws_get_timetable(hass_, connection, msg): # noqa: ANN001
207220

208221
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
209222
"""Set up of Bakalari component."""
210-
_dev_console_handler_for(
211-
logging.getLogger("custom_components.bakalari"), CustomFormatter()
212-
)
213223

214-
children = ChildrenIndex.from_entry(entry)
224+
children: ChildrenIndex = ChildrenIndex.from_entry(entry)
225+
226+
# create shared library for each child
227+
_clients: dict[str, BakalariClient] = {}
228+
for child in children.children:
229+
_LOGGER.debug(
230+
"[class=%s module=%s] Creating client for child: %s",
231+
object.__name__,
232+
__name__,
233+
child.display_name,
234+
)
235+
236+
_client = BakalariClient(
237+
hass, entry, children.option_key_for_child(child.key) or ""
238+
)
239+
_clients[child.key] = _client
215240

216-
coord_marks = BakalariMarksCoordinator(hass, entry, children)
217-
coord_msgs = BakalariMessagesCoordinator(hass, entry, children)
218-
coord_tt = BakalariTimetableCoordinator(hass, entry, children)
241+
coord_marks = BakalariMarksCoordinator(hass, entry, children, _clients)
242+
coord_msgs = BakalariMessagesCoordinator(hass, entry, children, _clients)
243+
coord_tt = BakalariTimetableCoordinator(hass, entry, children, _clients)
219244

220245
hass.data.setdefault(DOMAIN, {})
221246
hass.data[DOMAIN][entry.entry_id] = {
222247
"children": children,
248+
"clients": _clients,
223249
"marks": coord_marks,
224250
"messages": coord_msgs,
225251
"timetable": coord_tt,

custom_components/bakalari/api.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,12 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry, child_id: str):
6565
self._save_lock = asyncio.Lock()
6666
self._last_tokens: tuple[str, str] | None = None
6767

68-
_LOGGER.info(
68+
_LOGGER.debug(
6969
"[class=%s module=%s] Created BakalariClient instance for child_id=%s",
7070
self.__class__.__name__,
7171
__name__,
7272
self.child_id,
73+
stacklevel=2,
7374
)
7475

7576
def api_call(
@@ -214,12 +215,14 @@ async def _is_lib(self) -> Bakalari | None:
214215
self.lib = Bakalari(
215216
server=server, credentials=cred, session=session
216217
)
217-
_LOGGER.info(
218+
_LOGGER.debug(
218219
"[class=%s module=%s] Bakalari library instance created for child_id=%s With parameters: %s",
219220
self.__class__.__name__,
220221
__name__,
221222
self.child_id,
222223
redact_child_info(child),
224+
stacklevel=2,
225+
stack_info=True,
223226
)
224227

225228
self._last_tokens = self._snapshot_tokens()
@@ -519,3 +522,17 @@ async def async_sign_marks(self, lib, subjects: list[str]):
519522

520523
marks = Marks(lib)
521524
await marks.async_sign_marks(subjects)
525+
526+
@api_call(label="Mark message as read", default=None)
527+
async def message_mark_as_read(self, lib, mid: str):
528+
"""Mark message as read.
529+
530+
Call API clients mark message as read function.
531+
"""
532+
_LOGGER.debug(
533+
"[class=%s module=%s] Called mark message as read endpoint.",
534+
self.__class__.__name__,
535+
__name__,
536+
)
537+
komens: Komens = Komens(lib)
538+
await komens.message_mark_read(mid)

custom_components/bakalari/const.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
MANUFACTURER = "Bakaláři pro HomeAssistant"
99
MODEL = "Bakaláři backend"
1010

11-
LIB_VERSION: Final = "1.4.1"
11+
LIB_VERSION: Final = "1.5.0"
1212
API_VERSION: Final = "0.9.0"
1313
SW_VERSION: Final = f"API: {API_VERSION} Library: {LIB_VERSION}"
1414
CONF_CHILDREN: Final = "children"

custom_components/bakalari/coordinator_marks.py

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,18 @@ class BakalariMarksCoordinator(DataUpdateCoordinator[dict[str, Any]]):
3131
"""Coordinator for Bakaláři marks – minimal data model for sensors."""
3232

3333
def __init__(
34-
self, hass: HomeAssistant, entry: ConfigEntry, children_index: ChildrenIndex
34+
self,
35+
hass: HomeAssistant,
36+
entry: ConfigEntry,
37+
children_index: ChildrenIndex,
38+
clients: dict[str, BakalariClient],
3539
) -> None:
3640
"""Initialize the coordinator."""
3741
self.hass = hass
3842
self.entry = entry
39-
self.children_index = children_index
40-
self.child_list = list(children_index.children)
41-
42-
# api_version removed; integration uses central SW_VERSION for device info
43+
self.children_index: ChildrenIndex = children_index
44+
self.child_list = list(self.children_index.children)
45+
self._clients: dict[str, BakalariClient] = clients
4346

4447
# Diff cache per child: set of (child_key, mark_id)
4548
self._seen: set[tuple[str, str]] = set()
@@ -56,9 +59,6 @@ def __init__(
5659
update_interval=update_interval,
5760
)
5861

59-
# Per-child clients cache (BakalariClient instances)
60-
self._clients: dict[str, BakalariClient] = {}
61-
6262
_LOGGER.debug(
6363
"[class=%s module=%s] Marks coordinator ready (entry_id=%s, children=%d)",
6464
self.__class__.__name__,
@@ -67,6 +67,21 @@ def __init__(
6767
len(self.child_list),
6868
)
6969

70+
def get_client(self, child_key: str) -> BakalariClient | None:
71+
"""Return a client for a given child."""
72+
73+
client = self._clients[child_key] or None
74+
if not client:
75+
_LOGGER.error(
76+
"[class=%s module=%s] Failed to get client for child %s",
77+
self.__class__.__name__,
78+
__name__,
79+
child_key,
80+
stacklevel=2,
81+
)
82+
return None
83+
return client
84+
7085
# ---------- Public API for services / WebSocket ----------
7186

7287
async def async_mark_seen(self, mark_id: str, child_key: str | None) -> None:
@@ -141,13 +156,9 @@ async def _fetch_child(
141156
) -> dict[str, Any]:
142157
"""Fetch raw marks for a specific child via BakalariClient."""
143158

144-
# Map composite key to original options key (usually user_id)
145-
opt_key = self.children_index.option_key_for_child(child.key) or child.user_id
146-
147-
client = self._clients.get(child.key)
159+
client: BakalariClient | None = self.get_client(child.key)
148160
if client is None:
149-
client = BakalariClient(self.hass, self.entry, opt_key)
150-
self._clients[child.key] = client # cache per child
161+
return {}
151162

152163
# Marks snapshot (subjects, grouped, flat)
153164
dt_from = (
@@ -180,7 +191,10 @@ async def _fetch_child(
180191

181192
async def async_sign_marks(self, child_key, subjects: list[str]):
182193
"""Sign all marks for a child."""
183-
client = self._clients[child_key]
194+
client: BakalariClient | None = self.get_client(child_key)
195+
196+
if not client:
197+
return
184198

185199
try:
186200
await client.async_sign_marks(subjects)

0 commit comments

Comments
 (0)