Skip to content

Commit 75d7590

Browse files
committed
lazilly create shutdown event
1 parent b1e42bd commit 75d7590

File tree

2 files changed

+80
-1
lines changed

2 files changed

+80
-1
lines changed

src/textual_dev/_compat.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from __future__ import annotations
2+
3+
import sys
4+
from typing import Any, Generic, TypeVar, overload
5+
6+
__all__ = ["cached_property"]
7+
8+
if sys.version_info >= (3, 12):
9+
from functools import cached_property
10+
else:
11+
# based on the code from Python 3.14:
12+
# https://github.com/python/cpython/blob/
13+
# 5507eff19c757a908a2ff29dfe423e35595fda00/Lib/functools.py#L1089-L1138
14+
# Copyright (C) 2006 Python Software Foundation.
15+
# vendored under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 because
16+
# prior to Python 3.12 cached_property used a threading.Lock, which makes
17+
# it very slow.
18+
_T_co = TypeVar("_T_co", covariant=True)
19+
_NOT_FOUND = object()
20+
21+
class cached_property(Generic[_T_co]):
22+
def __init__(self, func: Callable[[Any, _T_co]]) -> None:
23+
self.func = func
24+
self.attrname = None
25+
self.__doc__ = func.__doc__
26+
self.__module__ = func.__module__
27+
28+
def __set_name__(self, owner: type[any], name: str) -> None:
29+
if self.attrname is None:
30+
self.attrname = name
31+
elif name != self.attrname:
32+
raise TypeError(
33+
"Cannot assign the same cached_property to two different names "
34+
f"({self.attrname!r} and {name!r})."
35+
)
36+
37+
@overload
38+
def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ...
39+
40+
@overload
41+
def __get__(
42+
self, instance: object, owner: type[Any] | None = None
43+
) -> _T_co: ...
44+
45+
def __get__(
46+
self, instance: object, owner: type[Any] | None = None
47+
) -> _T_co | Self:
48+
if instance is None:
49+
return self
50+
if self.attrname is None:
51+
raise TypeError(
52+
"Cannot use cached_property instance without calling __set_name__ on it."
53+
)
54+
try:
55+
cache = instance.__dict__
56+
except (
57+
AttributeError
58+
): # not all objects have __dict__ (e.g. class defines slots)
59+
msg = (
60+
f"No '__dict__' attribute on {type(instance).__name__!r} "
61+
f"instance to cache {self.attrname!r} property."
62+
)
63+
raise TypeError(msg) from None
64+
val = cache.get(self.attrname, _NOT_FOUND)
65+
if val is _NOT_FOUND:
66+
val = self.func(instance)
67+
try:
68+
cache[self.attrname] = val
69+
except TypeError:
70+
msg = (
71+
f"The '__dict__' attribute on {type(instance).__name__!r} instance "
72+
f"does not support item assignment for caching {self.attrname!r} property."
73+
)
74+
raise TypeError(msg) from None
75+
return val

src/textual_dev/service.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from textual._time import time
1919

2020
from textual_dev.renderables import DevConsoleHeader, DevConsoleLog, DevConsoleNotice
21+
from textual_dev._compat import cached_property
2122

2223
QUEUEABLE_TYPES = {"client_log", "client_spillover"}
2324

@@ -44,9 +45,12 @@ def __init__(
4445
self.verbose = verbose
4546
self.exclude = {name.upper() for name in exclude} if exclude else set()
4647
self.console = Console()
47-
self.shutdown_event = asyncio.Event()
4848
self.clients: list[ClientHandler] = []
4949

50+
@cached_property
51+
def shutdown_event(self) -> asyncio.Event:
52+
return asyncio.Event()
53+
5054
async def start(self) -> None:
5155
"""Starts devtools tasks"""
5256
self.size_poll_task = asyncio.create_task(self._console_size_poller())

0 commit comments

Comments
 (0)