943acbc573
Durcit la convention de nommage des projets (dérive constatée : 'Sliding Automation', 'code_versioning'... au lieu des formes canoniques). - trilium_api.py : projets_canoniques() lit le référentiel = valeurs du label projet sur les notes de type=projet (source unique, pas de constante en dur). Note-projet CodeVersioning créée (manquait). - mcp_server.py : _valider_projet() branché dans les 6 tools de création (add_decision/history/backlog, new_conversation, create_entite, add_skill). Refuse un projet non canonique (suggestion si faute) ou inconnu (renvoi au processus de création de projet). Ne verrouille pas si référentiel illisible. - lint_audit.py : VAL-nommage aligné sur le référentiel (attrape casse, espace ET snake_case ; l'ancien 'contient un espace' ratait code_versioning). - Données : 79 notes ré-étiquetées vers les 3 formes canoniques. Quality by design : l'erreur de nommage devient impossible à l'écriture, le Lint n'est plus que le filet de sécurité.
130 lines
3.7 KiB
Python
130 lines
3.7 KiB
Python
import inspect
|
|
import logging
|
|
import weakref
|
|
|
|
from ._asyncio_loop import get_running_loop, get_task_loop
|
|
|
|
|
|
class StreamSink:
|
|
def __init__(self, stream):
|
|
self._stream = stream
|
|
self._flushable = callable(getattr(stream, "flush", None))
|
|
self._stoppable = callable(getattr(stream, "stop", None))
|
|
self._completable = inspect.iscoroutinefunction(getattr(stream, "complete", None))
|
|
|
|
def write(self, message):
|
|
self._stream.write(message)
|
|
if self._flushable:
|
|
self._stream.flush()
|
|
|
|
def stop(self):
|
|
if self._stoppable:
|
|
self._stream.stop()
|
|
|
|
def tasks_to_complete(self):
|
|
if not self._completable:
|
|
return []
|
|
return [self._stream.complete()]
|
|
|
|
|
|
class StandardSink:
|
|
def __init__(self, handler):
|
|
self._handler = handler
|
|
|
|
def write(self, message):
|
|
raw_record = message.record
|
|
message = str(message)
|
|
exc = raw_record["exception"]
|
|
record = logging.getLogger().makeRecord(
|
|
raw_record["name"],
|
|
raw_record["level"].no,
|
|
raw_record["file"].path,
|
|
raw_record["line"],
|
|
message,
|
|
(),
|
|
(exc.type, exc.value, exc.traceback) if exc else None,
|
|
raw_record["function"],
|
|
{"extra": raw_record["extra"]},
|
|
)
|
|
if exc:
|
|
record.exc_text = "\n"
|
|
record.levelname = raw_record["level"].name
|
|
self._handler.handle(record)
|
|
|
|
def stop(self):
|
|
self._handler.close()
|
|
|
|
def tasks_to_complete(self):
|
|
return []
|
|
|
|
|
|
class AsyncSink:
|
|
def __init__(self, function, loop, error_interceptor):
|
|
self._function = function
|
|
self._loop = loop
|
|
self._error_interceptor = error_interceptor
|
|
self._tasks = weakref.WeakSet()
|
|
|
|
def write(self, message):
|
|
try:
|
|
loop = self._loop or get_running_loop()
|
|
except RuntimeError:
|
|
return
|
|
|
|
coroutine = self._function(message)
|
|
task = loop.create_task(coroutine)
|
|
|
|
def check_exception(future):
|
|
if future.cancelled() or future.exception() is None:
|
|
return
|
|
if not self._error_interceptor.should_catch():
|
|
raise future.exception()
|
|
self._error_interceptor.print(message.record, exception=future.exception())
|
|
|
|
task.add_done_callback(check_exception)
|
|
self._tasks.add(task)
|
|
|
|
def stop(self):
|
|
for task in self._tasks:
|
|
task.cancel()
|
|
|
|
def tasks_to_complete(self):
|
|
# To avoid errors due to "self._tasks" being mutated while iterated, the
|
|
# "tasks_to_complete()" method must be protected by the same lock as "write()" (which
|
|
# happens to be the handler lock). However, the tasks must not be awaited while the lock is
|
|
# acquired as this could lead to a deadlock. Therefore, we first need to collect the tasks
|
|
# to complete, then return them so that they can be awaited outside of the lock.
|
|
return [self._complete_task(task) for task in self._tasks]
|
|
|
|
async def _complete_task(self, task):
|
|
loop = get_running_loop()
|
|
if get_task_loop(task) is not loop:
|
|
return
|
|
try:
|
|
await task
|
|
except Exception:
|
|
pass # Handled in "check_exception()"
|
|
|
|
def __getstate__(self):
|
|
state = self.__dict__.copy()
|
|
state["_tasks"] = None
|
|
return state
|
|
|
|
def __setstate__(self, state):
|
|
self.__dict__.update(state)
|
|
self._tasks = weakref.WeakSet()
|
|
|
|
|
|
class CallableSink:
|
|
def __init__(self, function):
|
|
self._function = function
|
|
|
|
def write(self, message):
|
|
self._function(message)
|
|
|
|
def stop(self):
|
|
pass
|
|
|
|
def tasks_to_complete(self):
|
|
return []
|