2021-08-21 19:29:21 +02:00
|
|
|
import weakref
|
|
|
|
from abc import ABCMeta, abstractmethod
|
|
|
|
from typing import Any, ClassVar, Generic, MutableMapping, TypeVar
|
|
|
|
|
2023-10-12 19:43:45 +02:00
|
|
|
from typing_extensions import override
|
|
|
|
|
2021-08-21 19:29:21 +02:00
|
|
|
_KeyT = TypeVar("_KeyT")
|
|
|
|
_DataT = TypeVar("_DataT")
|
|
|
|
|
|
|
|
|
|
|
|
class BaseNotes(Generic[_KeyT, _DataT], metaclass=ABCMeta):
|
|
|
|
"""This class defines a generic type-safe mechanism for associating
|
|
|
|
additional data with an object (without modifying the original
|
|
|
|
object via subclassing or monkey-patching).
|
|
|
|
|
|
|
|
It was originally designed to avoid monkey-patching the Django
|
|
|
|
HttpRequest object, to which we want to associate computed state
|
|
|
|
(e.g. parsed state computed from the User-Agent) so that it's
|
|
|
|
available in code paths that receive the HttpRequest object.
|
|
|
|
|
|
|
|
The implementation uses a WeakKeyDictionary, so that the notes
|
|
|
|
object will be garbage-collected when the original object no
|
|
|
|
longer has other references (avoiding memory leaks).
|
|
|
|
|
|
|
|
We still need to be careful to avoid any of the attributes of
|
2022-02-08 00:13:33 +01:00
|
|
|
_DataT having points to the original object, as that can create a
|
2021-08-21 19:29:21 +02:00
|
|
|
cyclic reference cycle that the Python garbage collect may not
|
|
|
|
handle correctly.
|
|
|
|
"""
|
|
|
|
|
2022-10-08 07:17:26 +02:00
|
|
|
__notes_map: ClassVar[MutableMapping[Any, Any]]
|
|
|
|
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2022-10-08 07:17:26 +02:00
|
|
|
def __init_subclass__(cls, **kwargs: object) -> None:
|
|
|
|
super().__init_subclass__(**kwargs)
|
|
|
|
if not hasattr(cls, "__notes_map"):
|
|
|
|
cls.__notes_map = weakref.WeakKeyDictionary()
|
2021-08-21 19:29:21 +02:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_notes(cls, key: _KeyT) -> _DataT:
|
|
|
|
try:
|
|
|
|
return cls.__notes_map[key]
|
|
|
|
except KeyError:
|
|
|
|
cls.__notes_map[key] = cls.init_notes()
|
|
|
|
return cls.__notes_map[key]
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def set_notes(cls, key: _KeyT, notes: _DataT) -> None:
|
|
|
|
cls.__notes_map[key] = notes
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
@abstractmethod
|
2024-01-29 00:32:21 +01:00
|
|
|
def init_notes(cls) -> _DataT: ...
|