add proxy for the first part and try to develop the second part

This commit is contained in:
Pierre Geeraert
2025-12-03 12:08:56 +00:00
parent 6c34d510e5
commit 460f18d85a
1095 changed files with 358799 additions and 5 deletions
@@ -0,0 +1,159 @@
from __future__ import annotations
from ..exceptions import CodegenError
from ..objectmodel import Node
from ..rendering import Renderer, RenderingFormatter, render
__all__ = [
'DelegatingRenderingFormatter',
'ModelRenderer',
'NullModelRenderer',
'CodeGenerator',
]
class DelegatingRenderingFormatter(RenderingFormatter):
def __init__(self, delegate):
assert hasattr(delegate, 'render')
super().__init__()
self.delegate = delegate
# override
def render(self, item, join='', **fields):
result = self.delegate.render(item, join=join, **fields)
if result is None:
result = super().render(item, join=join, **fields)
return result
def convert_field(self, value, conversion):
if isinstance(value, Node):
return self.render(value)
else:
return super().convert_field(value, conversion)
class ModelRenderer(Renderer):
def __init__(self, codegen, node, template=None):
super().__init__(template=template)
self._codegen = codegen
self._node = node
self.formatter = codegen.formatter
self.__postinit__()
def __postinit__(self):
pass
def __getattr__(self, name):
try:
super().__getattr__(name)
except AttributeError:
if name.startswith('_'):
raise
return getattr(self.node, name)
@property
def node(self):
return self._node
@property
def codegen(self):
return self._codegen
@property
def context(self):
return self._codegen
def get_renderer(self, item):
return self.codegen.get_renderer(item)
def render(self, **fields):
template = fields.pop('template', None)
if isinstance(self.node, Node):
fields.update(
{
k: v
for k, v in vars(self.node).items()
if not k.startswith('_')
},
)
else:
fields.update(value=self.node)
return super().render(template=template, **fields)
class NullModelRenderer(ModelRenderer):
"""A `ModelRenderer` that generates nothing."""
template = ''
class CodeGenerator:
"""
A **CodeGenerator** is an abstract class that finds a
``ModelRenderer`` class with the same name as each model's node and
uses it to render the node.
"""
def __init__(self, modules=None):
self.formatter = DelegatingRenderingFormatter(self)
self._renderers = {}
if modules is not None:
self._renderers = self._find_module_renderers(modules)
def _find_module_renderers(self, modules):
result = {}
for module in modules:
for name, dtype in vars(module).items():
if not isinstance(dtype, type):
continue
if not issubclass(dtype, ModelRenderer):
continue
if dtype is not ModelRenderer:
result[name] = dtype
return result
def _find_renderer_class(self, node):
if not isinstance(node, Node):
return None
node_class_name = node.__class__.__name__
classes = [node.__class__]
while classes:
cls = classes.pop()
name = cls.__name__
if name in self._renderers:
renderer = self._renderers[name]
self._renderers[node_class_name] = renderer
return renderer
for base in cls.__bases__:
if base not in classes:
classes.append(base)
raise CodegenError(f'Renderer for {node_class_name} not found')
def get_renderer(self, item):
if not isinstance(item, Node):
return None
renderer_class = self._find_renderer_class(item)
if renderer_class is None:
raise CodegenError(
f'Renderer not found for {type(item).__name__}',
)
try:
assert issubclass(renderer_class, ModelRenderer)
return renderer_class(self, item)
except Exception as e:
raise type(e)(str(e), renderer_class.__name__) from e
def render(self, item, join='', **fields):
renderer = self.get_renderer(item)
if renderer is None:
return render(item, join=join, **fields)
return str(renderer.render(**fields))