fix: serialize QR token client operations
This commit is contained in:
@@ -12,6 +12,8 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from collections.abc import Generator
|
||||
from contextlib import contextmanager
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
from typing import Any, Protocol
|
||||
@@ -252,6 +254,26 @@ class PronoteClient:
|
||||
except Exception as exc:
|
||||
logger.debug("Échec de la persistance des credentials : %s", redact_exception(exc))
|
||||
|
||||
@contextmanager
|
||||
def _qr_token_operation_lock(self) -> Generator[None]:
|
||||
"""Verrouille un cycle d'authentification et de récupération QR/token.
|
||||
|
||||
Le verrou englobe le chargement du token, le login, l'opération de
|
||||
données et la persistance qui suit. Il est volontairement absent du
|
||||
mode ``password``, qui ne partage pas de fichier d'état de token.
|
||||
|
||||
:return: Un gestionnaire de contexte protégeant le cycle QR/token.
|
||||
:rtype: collections.abc.Generator[None, None, None]
|
||||
:raises PronoteAuthStateLockError: Si l'état QR/token est déjà utilisé
|
||||
par une autre opération.
|
||||
"""
|
||||
if self._settings.auth_mode != "qr_token" or self._auth_state is None:
|
||||
yield
|
||||
return
|
||||
|
||||
with self._auth_state.lock():
|
||||
yield
|
||||
|
||||
def _connect_password(self) -> pronotepy.Client:
|
||||
"""Connecte le client ``pronotepy`` en mode ``password``.
|
||||
|
||||
@@ -426,37 +448,38 @@ class PronoteClient:
|
||||
:return: Liste des messages des professeurs ; vide en cas d'erreur.
|
||||
:rtype: list[Message]
|
||||
"""
|
||||
try:
|
||||
client = self._connect()
|
||||
messages: list[Message] = []
|
||||
for discussion in client.discussions():
|
||||
for message in discussion.messages:
|
||||
messages.append(
|
||||
Message(
|
||||
id=message.id,
|
||||
type=MessageType.DISCUSSION,
|
||||
title=discussion.subject,
|
||||
content=message.content,
|
||||
author=message.author or "",
|
||||
date=message.created,
|
||||
read=message.seen,
|
||||
with self._qr_token_operation_lock():
|
||||
try:
|
||||
client = self._connect()
|
||||
messages: list[Message] = []
|
||||
for discussion in client.discussions():
|
||||
for message in discussion.messages:
|
||||
messages.append(
|
||||
Message(
|
||||
id=message.id,
|
||||
type=MessageType.DISCUSSION,
|
||||
title=discussion.subject,
|
||||
content=message.content,
|
||||
author=message.author or "",
|
||||
date=message.created,
|
||||
read=message.seen,
|
||||
)
|
||||
)
|
||||
)
|
||||
self._persist_credentials()
|
||||
return messages
|
||||
except (
|
||||
pronotepy.PronoteAPIError,
|
||||
ValueError,
|
||||
requests.RequestException,
|
||||
ConnectionError,
|
||||
TimeoutError,
|
||||
) as exc:
|
||||
logger.error(
|
||||
"Échec de la récupération des messages Pronote : %s",
|
||||
redact_exception(exc),
|
||||
)
|
||||
self._persist_credentials()
|
||||
return []
|
||||
self._persist_credentials()
|
||||
return messages
|
||||
except (
|
||||
pronotepy.PronoteAPIError,
|
||||
ValueError,
|
||||
requests.RequestException,
|
||||
ConnectionError,
|
||||
TimeoutError,
|
||||
) as exc:
|
||||
logger.error(
|
||||
"Échec de la récupération des messages Pronote : %s",
|
||||
redact_exception(exc),
|
||||
)
|
||||
self._persist_credentials()
|
||||
return []
|
||||
|
||||
def get_informations(self) -> list[Message]:
|
||||
"""Récupère les informations et sondages Pronote.
|
||||
@@ -467,36 +490,37 @@ class PronoteClient:
|
||||
:return: Liste des informations et sondages ; vide en cas d'erreur.
|
||||
:rtype: list[Message]
|
||||
"""
|
||||
try:
|
||||
client = self._connect()
|
||||
messages: list[Message] = []
|
||||
for info in client.information_and_surveys():
|
||||
messages.append(
|
||||
Message(
|
||||
id=info.id,
|
||||
type=MessageType.SURVEY if info.survey else MessageType.INFORMATION,
|
||||
title=info.title or "",
|
||||
content=info.content(),
|
||||
author=info.author,
|
||||
date=info.creation_date,
|
||||
read=info.read,
|
||||
with self._qr_token_operation_lock():
|
||||
try:
|
||||
client = self._connect()
|
||||
messages: list[Message] = []
|
||||
for info in client.information_and_surveys():
|
||||
messages.append(
|
||||
Message(
|
||||
id=info.id,
|
||||
type=MessageType.SURVEY if info.survey else MessageType.INFORMATION,
|
||||
title=info.title or "",
|
||||
content=info.content(),
|
||||
author=info.author,
|
||||
date=info.creation_date,
|
||||
read=info.read,
|
||||
)
|
||||
)
|
||||
self._persist_credentials()
|
||||
return messages
|
||||
except (
|
||||
pronotepy.PronoteAPIError,
|
||||
ValueError,
|
||||
requests.RequestException,
|
||||
ConnectionError,
|
||||
TimeoutError,
|
||||
) as exc:
|
||||
logger.error(
|
||||
"Échec de la récupération des informations Pronote : %s",
|
||||
redact_exception(exc),
|
||||
)
|
||||
self._persist_credentials()
|
||||
return messages
|
||||
except (
|
||||
pronotepy.PronoteAPIError,
|
||||
ValueError,
|
||||
requests.RequestException,
|
||||
ConnectionError,
|
||||
TimeoutError,
|
||||
) as exc:
|
||||
logger.error(
|
||||
"Échec de la récupération des informations Pronote : %s",
|
||||
redact_exception(exc),
|
||||
)
|
||||
self._persist_credentials()
|
||||
return []
|
||||
self._persist_credentials()
|
||||
return []
|
||||
|
||||
def get_lessons(self, start: date, end: date) -> list[Lesson]:
|
||||
"""Récupère les cours via ``pronotepy`` (repli iCal).
|
||||
@@ -523,37 +547,38 @@ class PronoteClient:
|
||||
:raises ConnectionError: Si la connexion réseau échoue.
|
||||
:raises TimeoutError: Si la requête réseau expire.
|
||||
"""
|
||||
client = self._connect()
|
||||
lessons: list[Lesson] = []
|
||||
for lesson in client.lessons(start, end):
|
||||
content = lesson.content
|
||||
raw_uid = lesson.id
|
||||
if raw_uid:
|
||||
uid = normalize_pronote_uid(raw_uid)
|
||||
else:
|
||||
uid = generate_deterministic_uid(
|
||||
start=lesson.start,
|
||||
end=lesson.end,
|
||||
subject=lesson.subject.name if lesson.subject is not None else "",
|
||||
teachers=list(lesson.teacher_names or ()),
|
||||
rooms=list(lesson.classrooms or ()),
|
||||
group=lesson.group_name,
|
||||
with self._qr_token_operation_lock():
|
||||
client = self._connect()
|
||||
lessons: list[Lesson] = []
|
||||
for lesson in client.lessons(start, end):
|
||||
content = lesson.content
|
||||
raw_uid = lesson.id
|
||||
if raw_uid:
|
||||
uid = normalize_pronote_uid(raw_uid)
|
||||
else:
|
||||
uid = generate_deterministic_uid(
|
||||
start=lesson.start,
|
||||
end=lesson.end,
|
||||
subject=lesson.subject.name if lesson.subject is not None else "",
|
||||
teachers=list(lesson.teacher_names or ()),
|
||||
rooms=list(lesson.classrooms or ()),
|
||||
group=lesson.group_name,
|
||||
)
|
||||
lessons.append(
|
||||
Lesson(
|
||||
id=uid,
|
||||
start=lesson.start,
|
||||
end=lesson.end,
|
||||
subject=lesson.subject.name if lesson.subject is not None else "",
|
||||
teachers=tuple(lesson.teacher_names or ()),
|
||||
rooms=tuple(lesson.classrooms or ()),
|
||||
group=lesson.group_name,
|
||||
status=(LessonStatus.CANCELLED if lesson.canceled else LessonStatus.NORMAL),
|
||||
content=content.description if content is not None else None,
|
||||
)
|
||||
)
|
||||
lessons.append(
|
||||
Lesson(
|
||||
id=uid,
|
||||
start=lesson.start,
|
||||
end=lesson.end,
|
||||
subject=lesson.subject.name if lesson.subject is not None else "",
|
||||
teachers=tuple(lesson.teacher_names or ()),
|
||||
rooms=tuple(lesson.classrooms or ()),
|
||||
group=lesson.group_name,
|
||||
status=(LessonStatus.CANCELLED if lesson.canceled else LessonStatus.NORMAL),
|
||||
content=content.description if content is not None else None,
|
||||
)
|
||||
)
|
||||
self._persist_credentials()
|
||||
return lessons
|
||||
self._persist_credentials()
|
||||
return lessons
|
||||
|
||||
def get_homeworks(self, start: date, end: date) -> list[Homework]:
|
||||
"""Récupère les devoirs via ``pronotepy``.
|
||||
@@ -576,19 +601,20 @@ class PronoteClient:
|
||||
:raises ConnectionError: Si la connexion réseau échoue.
|
||||
:raises TimeoutError: Si la requête réseau expire.
|
||||
"""
|
||||
client = self._connect()
|
||||
homeworks: list[Homework] = []
|
||||
for hw in client.homework(start, end):
|
||||
homeworks.append(
|
||||
Homework(
|
||||
id=hw.id,
|
||||
subject=hw.subject.name,
|
||||
teachers=(),
|
||||
assigned_on=None,
|
||||
due_on=hw.date,
|
||||
text=hw.description,
|
||||
html=hw.description,
|
||||
with self._qr_token_operation_lock():
|
||||
client = self._connect()
|
||||
homeworks: list[Homework] = []
|
||||
for hw in client.homework(start, end):
|
||||
homeworks.append(
|
||||
Homework(
|
||||
id=hw.id,
|
||||
subject=hw.subject.name,
|
||||
teachers=(),
|
||||
assigned_on=None,
|
||||
due_on=hw.date,
|
||||
text=hw.description,
|
||||
html=hw.description,
|
||||
)
|
||||
)
|
||||
)
|
||||
self._persist_credentials()
|
||||
return homeworks
|
||||
self._persist_credentials()
|
||||
return homeworks
|
||||
|
||||
Reference in New Issue
Block a user