Source code for grave_settings.semantics

import os
from typing import Generic, Type, TypeVar, Callable, Any, Iterable, Self

from grave_settings.utilities import T, format_class_str


[docs]class SymantecNotSupportedError(Exception): pass
[docs]class SymantecConfigurationInvalid(Exception): pass
[docs]class SecurityException(Exception): pass
[docs]class OmitMeError(Exception): """ raise this to retroactively refuse to be serialized as an attribute or dictionary key """ pass
[docs]class Negate: __slots__ = 'semantic', def __init__(self, semantic): self.semantic = semantic def __neg__(self): return self.semantic
[docs]class Semantic(Generic[T]): """ Semantics are meant to be "frozen" in that they do not change state after they have been initialized. They may be passed from context to context without respect for consistency or state and are expected to always have the same meaning no matter where and "when" they are. """ __slots__ = 'val', COLLECTION: Type[set] | None = None C_T = TypeVar('C_T', bound=COLLECTION) def __init__(self, value: T): self.val = value
[docs] def collection_add(self, collection): collection.add(self)
[docs] def collection_remove(self, collection): collection.remove(self)
[docs] @staticmethod def collection_copy(collection) -> C_T: return collection.copy()
[docs] @staticmethod def collection_concatenate(first: C_T, second: C_T): return first | second
def __bool__(self): return bool(self.val) def __eq__(self, other): if type(self) != type(other): return False if other.val != self.val: return False return True def __hash__(self): return hash(hash(self.__class__) + hash(self.val)) def __str__(self): return f'{self.__class__.__name__}({self.val})' def __repr__(self): return f'{self.__class__.__name__}({repr(self.val)})' def __invert__(self): return Negate(self)
T_S = TypeVar('T_S', bound=Semantic) T_S_E = TypeVar('T_S_E', bound=Semantic | Negate)
[docs]class Semantics: def __init__(self, semantics: dict[Type[T_S], T_S | set[T_S]] = None): if semantics is None: semantics = {} self.semantics = semantics self.parent: None | Semantics = None
[docs] def update(self, semantics: Iterable[T_S_E]): self.add_semantics(*semantics)
[docs] def add_semantics(self, *semantics: T_S_E): dict_obj = self.semantics for semantic in semantics: if type(semantic) is Negate: if type(semantic.semantic) is type: self.pop(semantic.semantic) else: self.remove_semantic(semantic.semantic) continue if semantic.COLLECTION is None: dict_obj[semantic.__class__] = semantic else: smc = semantic.__class__ if smc in dict_obj: semantic.collection_add(dict_obj[smc]) else: collect = smc.COLLECTION() semantic.collection_add(collect) dict_obj[smc] = collect
def __getitem__(self, semantic_class: Type[T_S]) -> T_S | list[T_S] | None: if self.parent is None: return self.get_semantic(semantic_class) else: if (ps := self.parent.get_semantic(semantic_class)) is not None: if semantic_class.COLLECTION is None: return ps else: if s := self.get_semantic(semantic_class): return semantic_class.collection_concatenate(ps, s) # order matters for overwrite else: return ps return self.get_semantic(semantic_class)
[docs] def get_semantic(self, semantic_class: Type[T_S]) -> T_S | list[T_S] | None: if semantic_class in self.semantics: return self.semantics[semantic_class]
def __delitem__(self, key: Type[T_S]): self.pop(key)
[docs] def pop(self, key: Type[T_S]): return self.semantics.pop(key)
[docs] def remove_semantic(self, semantic: Semantic): smc = semantic.__class__ dict_obj = self.semantics if smc in dict_obj: if smc.COLLECTION is None: if dict_obj[smc].val == semantic.val: del self[smc] else: semantics = dict_obj[smc] items = tuple(ins for ins in semantics if ins.val == semantic.val) if len(semantics) == len(items): del self[smc] for item in reversed(items): item.collection_remove(semantics)
def __contains__(self, item: Type[Semantic] | Semantic) -> bool: if self.parent is not None: if item in self.parent: return True if isinstance(item, Semantic): if item.COLLECTION is None: return self[item.__class__] == item else: if (v := self[item.__class__]) is None: return False return item in v else: return item in self.semantics
[docs] def copy(self) -> Self: sems = self.__class__(semantics=self.semantics.copy()) return sems
def __str__(self): sems = set() for semantic in self.semantics.values(): if isinstance(semantic, Semantic): sems.add(str(semantic)) else: sems.update(str(s) for s in semantic) sems_str = ', '.join(sems) return f'{self.__class__.__name__}({sems_str}, parent={str(self.parent)})' def __bool__(self): return len(self.semantics) > 0 or len(self.parent) > 0 def __len__(self): return len(self.semantics) def __iter__(self): for sem in self.semantics.values(): if isinstance(sem, Semantic): yield sem else: yield from sem
[docs]class SemanticContext(Semantics): def __init__(self, semantics: Semantics): super().__init__(semantics=semantics.semantics.copy()) self.stack = []
[docs] def add_frame_semantics(self, *semantic: T_S_E): if self.parent is None: self.parent = Semantics() self.parent.add_semantics(*semantic)
[docs] def remove_frame_semantic(self, semantic: Type[Semantic] | Semantic): if self.parent is not None: if type(semantic) is type: self.parent.pop(semantic) else: self.parent.remove_semantic(semantic)
[docs] def add_semantics(self, *semantics: T_S_E): if self.semantics is None: self.semantics = {} return super().add_semantics(*semantics)
[docs] def get_semantic(self, semantic_class: Type[T_S]) -> T_S | list[T_S] | None: if self.semantics is None: return None return super().get_semantic(semantic_class)
def __getitem__(self, semantic_class: Type[T_S]) -> T_S | list[T_S] | None: if self.semantics is None: if self.parent is None: return None return self.parent[semantic_class] return super().__getitem__(semantic_class)
[docs] def remove_semantic(self, semantic: Type[Semantic] | Semantic): if self.semantics is None: return super().remove_semantic(semantic)
[docs] def copy_semantics(self): sems = self.semantics.copy() for k, v in sems.items(): if k.COLLECTION is not None: sems[k] = k.collection_copy(v) return sems
[docs] def context_push(self): self.stack.append(self.copy_semantics()) self.stack.append(self.parent) self.parent = None
[docs] def context_pop(self): self.parent = self.stack.pop(-1) self.semantics = self.stack.pop(-1)
def __enter__(self) -> Self: self.context_push() return self def __exit__(self, exc_type, exc_val, exc_tb): self.context_pop() def __str__(self): bs = [f'Semantic Context ({format_class_str(self.__class__)})'] for s in self.stack: bs.append(f'\t{s}') return os.linesep.join(bs)
[docs]class IgnoreDuckTypingForType(Semantic[Type]): """ Disables duck typing in the formatter for a specific class. This is to take care of naming clashes with types that happen to share the same name as the built-in methods """ pass
[docs]class IgnoreDuckTypingForSubclasses(Semantic[Type]): """ Disables duck typing in the formatter for a specific class. This is to take care of naming clashes with types that happen to share the same name as the built-in methods """ pass
[docs]class OmitMe(Semantic): """ Used to inform that an object's parent should not include this object as a member. It's probably a bad idea to overuse this, but it is meant for flagging objects that should never be serialized or represented in an objects state """ def __init__(self): raise NotImplementedError('This is not meant to be instantiated. Raise OmitMeError if you want this functionality')
[docs]class PreserveDictionaryOrdering(Semantic[bool]): """ Keep the ordering of dictionary objects consistent between the format and the python object hierarchy """ pass
[docs]class PreserveSerializableKeyOrdering(Semantic[bool]): """ Similar to PreserveDictionaryOrdering but for serializable objects. This includes auto-serialized objects. """ pass
[docs]class OverrideClassString(Semantic[str]): """ The default class string is overriden by a custom value when serializing """ pass
[docs]class SerializeNoneVersionInfo(Semantic[bool]): """ If this is False then versioned objects that have null version information will not serialize their version information. The result is a cleaner file, but it is not always the case that null version information is the same as being unversioned """ pass
[docs]class AutoKeySerializableDictType(Semantic[Type]): """ Automatically scan dictionary objects to ensure their keys are serializable as native format keys. If not they are replaced by a wrapper type whose factory is supplied to this semantic's constructor """ pass
[docs]class Indentation(Semantic[int]): """ Specified indentation formatting if applicable """ pass
[docs]class AutoPreserveReferences(Semantic[bool]): """ The formatter will keep track of objects that are referenced more than once in the object hierarchy and automatically convert subsequent instanced of the same object to a PreservedReference """ pass
[docs]class EnforceReferenceLifecycle(Semantic[bool]): """ Ensures that an object id that is used to cache an object for PreservedReferences is not re-used by the interpreter by maintaining a reference to all objects cached for the duration of the operation. """ pass
[docs]class DetonateDanglingPreservedReferences(Semantic[bool]): """ This will call a method that raises an exception if any tracked PreservedReference has not been flagged for garbage collection at the end of the deserialization process. It can be used to test if all the PreservedReference objects have been replaced by their correct reference since they should all be de-referenced by the end of the process. """ pass
[docs]class ResolvePreservedReferences(Semantic[bool]): """ Preserved References are resolved by the formatter and never given to the object. This may be slower. but it ensures that the object will never have a property set that is of type PreservedReference. When this is not present the formatter should not resolve the preserved references. Objects can resolve them by subscribing to the context objects """ pass
[docs]class NotifyFinalizedMethodName(Semantic[str]): """ This can be used as a frame semantic while de-serializing to get a callback on a method designated by the argument. The argument should be the method name as it is a member of the current object. The signature or the callback should match the signature of Serializable's finalize method. currently: (self, id_map: dict) -> None: The id_map will be a dictionary of reference ids to de-serialized objects. The references ids should be consistent with PreservedReference's "ref" member variable. """ pass
[docs]class DoNotAllowImportingModules(Semantic[bool]): """ When de-serializing, do not import modules that are not currently loaded in the system path. This will disallow the loading of arbitrary python modules if they are not already loaded. """ pass
[docs]class ClassStringPassFunction(Semantic[Callable[[str], bool]]): """ Define a function that will return a boolean specifying the acceptability of a class path string. If the function returns false the class/module will not be imported, executed or instantiated and instead a SecurityException will be raised. """ COLLECTION = set
[docs]class KeySemanticsTemplate(Semantic[dict[Any, Iterable[Semantic]]]): pass