# - * -coding: utf - 8 - * -
"""
@author: ☙ Ryan McConnell ❧
"""
from abc import abstractmethod
from typing import TypeVar, MutableMapping, Type, Mapping, Callable, Self, Generator, Literal
from observer_hooks import notify
from grave_settings.conversion_manager import ConversionManager
from grave_settings.formatter_settings import FormatterContext, PreservedReference
from grave_settings.validation import SettingsValidator
_KT = TypeVar('_KT')
_VT = TypeVar('_VT')
[docs]class Serializable:
__slots__ = '__weakref__',
[docs] @classmethod
def check_in_serialization_context(cls, context: FormatterContext):
"""
This method typically is called on an instantiated object, so it may not be necessary for it to be a class
method.
"""
pass
[docs] @classmethod
def check_in_deserialization_context(cls, context: FormatterContext):
"""
This method is typically called on type objects, so it should remain a class method in sub-classes. This is where
you can put a reference to :py:class:`~grave_settings.semantics.NotifyFinalizedMethodName` to get a callback
"""
# context.register_frame_semantic(NotifyFinalizedMethodName('finalize'))
pass
[docs] def to_dict(self, context: FormatterContext, **kwargs) -> dict:
zgen = ((i, getattr(self, i)) for i in dir(self))
return dict(i for i in zgen if not (callable(i[1]) or i[0].startswith('__')))
[docs] def from_dict(self, state_obj: dict, context: FormatterContext, **kwargs):
for k, v in state_obj.items():
setattr(self, k, v)
[docs] def finalize(self, frame: FormatterContext) -> None: # This is pretty inefficient. Override it
for key in dir(self):
v = getattr(self, key)
if isinstance(v, PreservedReference):
setattr(self, key, frame.find(v))
[docs]class VersionedSerializable(Serializable):
VERSION = None
__slots__ = tuple()
[docs] @classmethod
def get_version(cls):
return cls.VERSION
[docs] @classmethod
def get_version_object(cls):
"""
It is saf to override this as an instance method
"""
return cls.get_conversion_manager().get_version_object(cls)
[docs] @classmethod
def get_conversion_manager(cls) -> ConversionManager:
cm = ConversionManager()
cm.converted.subscribe(cls.conversion_manager_converted)
return cm
[docs] @classmethod
def check_convert_update(cls, state_obj: dict, load_type: Callable[[str], Type],
version_obj: dict) -> dict | Literal[False]:
conversion_manager = cls.get_conversion_manager()
rst = state_obj
instance = conversion_manager.update_to_current(state_obj, load_type, version_obj)
if rst is instance:
return False
else:
return instance
[docs] @classmethod
def conversion_manager_converted(cls, state_obj: dict, class_str: str, ver: str, target_ver: str = None):
pass
[docs] @classmethod
def get_versioning_endpoint(cls) -> Type[Self]:
return VersionedSerializable
[docs]def make_kill_converter(cls: Type[VersionedSerializable]) -> Callable[[dict], dict]:
return lambda _: cls().to_dict(explicit=True)
[docs]class IASettings(VersionedSerializable, MutableMapping):
__slots__ = 'parent', '_invalidate', 'file_path'
def __init__(self, *args, initialize_settings=True, **kwargs):
self.parent: IASettings | None = None
if initialize_settings:
self.init_settings(**kwargs)
[docs] def init_settings(self, **kwargs) -> None:
pass
[docs] def get_versioning_endpoint(self) -> Type[VersionedSerializable]:
return IASettings
@notify()
def invalidate(self) -> None:
if self.parent is not None:
self.parent.invalidate()
[docs] def update(self, mapping_obj: Mapping[_KT, _VT], **kwargs: _VT):
it_t = type(mapping_obj)
if it_t == list or it_t == tuple:
for k, v in mapping_obj:
self[k] = v
else:
for k, v in mapping_obj.items():
self[k] = v
for k, v in kwargs.items():
self[k] = v
[docs] def finalize(self, frame: FormatterContext):
for key, v in self.generate_key_value_pairs():
if isinstance(v, PreservedReference):
self[key] = frame.find(v)
@abstractmethod
def __contains__(self, item):
pass
@abstractmethod
def __setitem__(self, key, value):
pass
@abstractmethod
def __getitem__(self, item):
pass
@abstractmethod
def __delitem__(self, itm):
pass
@abstractmethod
def __iter__(self):
pass
@abstractmethod
def __len__(self) -> int:
pass
def __bool__(self): # This is to avoid unexpected edge case bugs since __len__ is implemented. Maybe just use explicit checks
return True
[docs] @abstractmethod
def generate_key_value_pairs(self, **kwargs) -> Generator[tuple[object, object], None, None]:
pass
[docs] def get_validator(self) -> SettingsValidator | None:
pass
def __hash__(self): # override this if you want to support value based equality
return hash(id(self))