@SingleBlock.setup_dispatch(from_key="_full_class", allow_self=False)
class TemplateBlock(SingleBlock):
_id_key = "template_name"
_full_class = None
_fields = None
def __init__(self, blockDict, **kwargs):
self._val_exceptions = {}
from ._blockUtils import _unwrap_block
blockDict = _unwrap_block(blockDict)
blockDict.setdefault("template_name", self.__class__.__name__)
super().__init__(blockDict, **kwargs)
def _override_validators(self, validators):
from .blocks import Block
try:
rename_dict = self.rename_dict()
except AttributeError:
rename_dict = {}
for field in self._fields:
if field in rename_dict:
field = rename_dict[field]
if field not in validators:
continue
val = validators[field]
if not isinstance(val, type) or not issubclass(val, Block):
new_val = TemplateField
elif issubclass(val, SingleBlock):
new_val = val.TemplateClass()
elif issubclass(val, ListBlock):
new_val = TemplateList.Simple(val._reqCls)
else:
raise NotImplementedError("Shouldn't happen")
validators[field] = new_val
for field in self._val_exceptions:
validators[field] = FailedTemplateField
return validators
def get_req_validators(self):
validators = super().get_req_validators()
return self._override_validators(validators)
def get_validators(self):
validators = super().get_validators()
return self._override_validators(validators)
def find_staged_id(self):
if not (hasattr(self, "_staged_parent")
and self._staged_parent.has_staged()):
return False
staged_dict = self._staged_parent._staged_templates
staged_reversed = {v:k for k,v in staged_dict.items()}
return staged_reversed.get(self, False)
def fill(self,incomplete=False,staged=False,in_place=False,**kwargs):
if not self._full_class is not None and issubclass(self._full_class, SingleBlock):
raise TypeError("A Template Block must be initialized from an existing class in order to be filled.")
for prop in self._staged_templates:
self.fill_staged_template(prop)
staged_id = self.find_staged_id()
if staged_id and not staged:
_, filled = self._staged_parent.fill_staged_template(staged_id, **kwargs)
return filled
serial = self.serialize(keepListType=True)
for kw, arg in kwargs.items():
serial[kw] = arg
flex_cls = self._full_class.TemplateClass()
try:
filled = self._full_class(serial)
except Exception as e:
filled = flex_cls(serial)
return filled
def serialize(self,keepListType=False, shallow=False, clean=False, **kwargs):
# from ..parsing.validation import required_keys
# from ..parsing.format_fos import format_field
required = self.get_req_validators()
required.pop('ext',None)
required.pop('template_name',None)
serial = super().serialize(keepListType=keepListType, shallow=shallow, clean=clean)
out = {"template_name":serial.pop("template_name","")}
for key, staged in self._staged_templates.items():
serial.setdefault(key, staged.serialize(keepListType=keepListType, shallow=shallow, clean=clean))
for key,validator in required.items():
val = None
if isinstance(validator,type):
if issubclass(validator,SingleBlock):
val = serial.pop(key, validator.reflex())
elif issubclass(validator, ListBlock):
val = serial.pop(key, validator([]).serialize())
if val is None:
val = serial.pop(key, TemplateField("").serialize())
out[key] = val
for key, val in serial.items():
out[key] = val
return out
def __setattr__(self, name, value):
from .. import _errors as err
from .blocks import Block
try:
super().__setattr__(name, value)
self._val_exceptions.pop(name, None)
except err.FailedValidatorError as e:
validators = self.get_validators()
cached_val = validators.get(name, None)
if not isinstance(cached_val, type) or not issubclass(cached_val, Block):
self._val_exceptions[name] = e
# newly mutated _val_exceptions should allow setattr now.
super().__setattr__(name, value)
elif issubclass(cached_val, SingleBlock):
if isinstance(value, TemplateField) or value == TemplateField().serialize():
value = {}
self.stage_template(name, value)
elif issubclass(cached_val, TemplateList):
raise NotImplementedError("A TemplateList construction failed unexpectedly.")
else: # ListBlock Only
from ._blockUtils import _unwrap_listblock
from warnings import warn
setattr(self, name, [])
value = _unwrap_listblock(value)
new_listblock = getattr(self, name)
warnings = []
for item in value:
try:
new_listblock.append(item)
except err.FailedValidatorError as e:
try:
new_listblock.stage_template(template=item)
except Exception as e:
warnings.append("The following item could not be set to a ListBlock or staged as a template:"
f"\n\nCANDIDATE:\n{item}"
f"\n\nERROR:\n{e}")
if warnings:
for w in warnings:
warn(w, UserWarning)
@classmethod
def TemplateClass(cls, *args):
if None in (cls._full_class, cls._fields):
raise TypeError("A new Template Block must be initialized from an existing class, or a Template of that class.")
fields = list(cls._fields)
fields.extend([a for a in args if a not in fields])
return cls._full_class.TemplateClass(*fields)
@classmethod
def _inject_defaults(cls, full_class, blockDict):
from .. import _errors as err
full_dispatch = getattr(full_class, "__dispatch__", {})
next_class = None
while next_class is not full_class:
if next_class is None:
next_class = full_dispatch.get("dispatch_from", full_class)
else:
registry = next_class.__dispatch__["registry"]
try:
next_class = next(sub for sub in registry.values() if issubclass(full_class, sub))
except StopIteration:
err.BlockDispatchError(
f"Could not find a valid dispatch chain to get from {next_class.__name__} to "
f"{full_class.__name__}.")
blockDict = next_class.inject_defaults(blockDict)
return blockDict
def __new__(cls, blockDict, *args, **kwargs):
from .. import _errors as err
if None in (cls._fields, cls._full_class):
raise err.BlockDispatchError("A Template Block must be initialized from an existing class, or a Template of that class.")
dispatched = kwargs.pop("_dispatched", False)
if dispatched:
blockDict.setdefault("template_name", cls.__name__)
return super().__new__(cls, blockDict, *args, _dispatched=True, **kwargs)
full_class = cls._full_class
blockDict = cls._inject_defaults(full_class, blockDict)
template_class = full_class.TemplateClass(*cls._fields)
for field in cls._fields:
if blockDict.get(field, None) is None:
blockDict[field] = TemplateField()
return template_class(blockDict, *args, _dispatched=True, **kwargs)