Coverage for gws-app/gws/core/tree_impl.py: 93%
255 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 12:46 +0200
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 12:46 +0200
1"""Internal implementations of ``gws.Node`` and ``gws.Root`` methods."""
3from . import (
4 const as c,
5 util as u,
6 log,
7)
9Access = None
10Error = None
11Data = None
12Props = None
13Object = None
16def object_repr(self):
17 r = getattr(self, 'extName', None) or class_name(self)
18 s = getattr(self, 'title', None)
19 if s:
20 r += f' title={s!r}'
21 s = getattr(self, 'uid', None)
22 if s:
23 r += f' uid={s}'
24 return '<' + r + ' ' + hex(id(self)) + '>'
27def node_initialize(self, config):
28 self.config = config
29 self.permissions = configure_permissions(self)
30 super_invoke(self, 'pre_configure')
31 super_invoke(self, 'configure')
34def node_create_child(self, classref, config, **kwargs):
35 return self.root.create(classref, parent=self, config=config, **kwargs)
38def node_create_child_if_configured(self, classref, config=None, **kwargs):
39 if not config:
40 return None
41 return self.root.create(classref, parent=self, config=config, **kwargs)
44def node_create_children(self, classref, configs, **kwargs):
45 if not configs:
46 return []
47 return u.compact(self.create_child(classref, cfg, **kwargs) for cfg in configs)
50def node_cfg(self, key: str, default=None):
51 val = u.get(self.config, key)
52 return val if val is not None else default
55def node_find_all(self, classref):
56 return find_all_in(self.root, self.children, classref)
59def node_find_first(self, classref):
60 return find_first_in(self.root, self.children, classref)
63def node_find_closest(self, classref):
64 node = self.parent
65 while True:
66 if not node or node is self.root:
67 return
68 if not classref or is_a(self.root, node, classref):
69 return node
70 node = node.parent
73def node_find_ancestors(self, classref):
74 ls = []
75 node = self.parent
77 while True:
78 if not node or node is self.root:
79 break
80 if not classref or is_a(self.root, node, classref):
81 ls.append(node)
82 node = node.parent
84 return ls
87def node_find_descendants(self, classref):
88 ls = []
90 def _walk(node):
91 for child_node in node.children:
92 if not classref or is_a(self.root, child_node, classref):
93 ls.append(child_node)
94 _walk(child_node)
96 _walk(self)
97 return ls
100##
103def root_init(self, specs):
104 self.specs = specs
105 self.app = None
106 self.permissions = {}
107 self.configErrors = []
108 self.configStack = []
109 self.configPaths = []
110 self.nodes = []
111 self.uidMap = {}
112 self.uidCount = 1
115def root_initialize(self, node, config):
116 self.configStack.append(node)
118 try:
119 node.initialize(config)
120 ok = True
121 except Exception as exc:
122 log.exception()
123 register_config_error(self, exc)
124 ok = False
126 self.configStack.pop()
127 return ok
130def root_post_initialize(self):
131 for node in reversed(self.nodes):
132 self.configStack = []
133 p = node
134 while p:
135 self.configStack.insert(0, p)
136 p = getattr(p, 'parent', None)
137 try:
138 super_invoke(node, 'post_configure')
139 except Exception as exc:
140 log.exception()
141 register_config_error(self, exc)
144def root_activate(self):
145 for node in self.nodes:
146 # if type(node).activate != Node.activate:
147 # log.debug(f'activate: {node!r}')
148 node.activate()
151def root_find_all(self, classref):
152 return find_all_in(self, self.nodes, classref)
155def root_find_first(self, classref):
156 return find_first_in(self, self.nodes, classref)
159def root_get(self, uid, classref):
160 if not uid:
161 return
162 node = self.uidMap.get(uid)
163 if node and (not classref or is_a(self, node, classref)):
164 return node
167def root_object_count(self) -> int:
168 return len(self.nodes)
171def root_create(self, classref, parent, config, **kwargs):
172 config = to_config(config, kwargs)
173 return create_node(self, classref, parent, config)
176def root_create_shared(self, classref, config, **kwargs):
177 config = to_config(config, kwargs)
179 uid = config.uid
180 if not uid:
181 config.uid = '_s_' + u.sha256([repr(classref), config])
183 if config.uid in self.uidMap:
184 return self.uidMap[config.uid]
186 return create_node(self, classref, None, config)
189def root_create_temporary(self, classref, config, **kwargs):
190 config = to_config(config, kwargs)
191 return create_node(self, classref, None, config, temp=True)
194def root_create_application(self, config, **kwargs):
195 config = to_config(config, kwargs)
197 node = alloc_node(self, 'gws.base.application.core.Object')
198 node.uid = c.APPLICATION_UID
199 node.parent = self
200 node.children = []
202 self.nodes.append(node)
203 self.uidMap[node.uid] = node
204 self.app = node
206 self.initialize(node, config)
208 return node
211##
214def class_name(node):
215 return node.__class__.__module__ + '.' + node.__class__.__name__
218def alloc_node(self, classref, typ=None):
219 cls = self.specs.get_class(classref, typ)
220 if not cls:
221 raise Error(f'class {classref}:{typ} not found')
223 node = cls()
224 node.root = self
225 node.extName = getattr(cls, 'extName', '')
226 node.extType = getattr(cls, 'extType', '')
228 return node
231def configure_permissions(self):
232 perms = {
233 Access.read: [],
234 Access.write: [],
235 Access.create: [],
236 Access.delete: [],
237 }
239 p = self.cfg('access')
240 if p:
241 perms[Access.read] = u.parse_acl(p)
243 p = self.cfg('permissions')
244 if p:
245 if isinstance(p, Data):
246 p = vars(p)
247 v = p.get('all')
248 if v:
249 perms[Access.read] = perms[Access.write] = perms[Access.create] = perms[Access.delete] = u.parse_acl(v)
251 v = p.get('edit')
252 if v:
253 perms[Access.write] = perms[Access.create] = perms[Access.delete] = u.parse_acl(v)
255 for k in {Access.read, Access.write, Access.create, Access.delete}:
256 v = p.get(k)
257 if v:
258 perms[k] = u.parse_acl(v)
260 return perms
263def create_node(self, classref, parent, config, temp=False):
264 node = alloc_node(self, classref, config.get('type'))
265 node.uid = get_or_generate_uid(self, config)
266 node.parent = parent
267 node.children = []
269 log.debug('configure: ' + ('.' * 4 * len(self.configStack)) + f'{node!r} IN {parent or self!r}')
270 ok = self.initialize(node, config)
271 if not ok:
272 log.debug(f'FAILED {node!r}')
273 return
275 if not temp:
276 self.nodes.append(node)
277 self.uidMap[node.uid] = node
279 if parent:
280 parent.children.append(node)
282 return node
285def find_all_in(root, nodes, classref):
286 if not classref:
287 return nodes
288 cls, name, ext_name = root.specs.parse_classref(classref)
289 if cls:
290 return [node for node in nodes if isinstance(node, cls)]
291 if name:
292 return [node for node in nodes if class_name(node) == name]
293 if ext_name:
294 return [node for node in nodes if node.extName.startswith(ext_name)]
297def find_first_in(root, nodes, classref):
298 found = find_all_in(root, nodes, classref)
299 return found[0] if found else None
302def get_or_generate_uid(self, config):
303 if config.get('uid'):
304 return config.get('uid')
305 self.uidCount += 1
306 return str(self.uidCount)
309def is_a(root, node, classref):
310 cls, name, ext_name = root.specs.parse_classref(classref)
311 if cls:
312 return isinstance(node, cls)
313 if name:
314 return class_name(node) == name
315 if ext_name:
316 return node.extName == ext_name or node.extName.startswith(ext_name + '.')
317 return False
320def props_of(node, user, *context):
321 if not user.can_use(node, *context):
322 return None
323 p = make_props2(node, user)
324 if p is None or isinstance(p, Data):
325 return p
326 if isinstance(p, dict):
327 return Props(p)
328 raise Error('invalid props type')
331def make_props2(obj, user):
332 if u.is_atom(obj):
333 return obj
335 if isinstance(obj, Object):
336 if user.acl_bit(Access.read, obj) == c.DENY:
337 return None
338 obj = obj.props(user)
340 if isinstance(obj, Data):
341 obj = vars(obj)
343 if u.is_dict(obj):
344 return u.compact({k: make_props2(v, user) for k, v in obj.items()})
346 if u.is_list(obj):
347 return u.compact([make_props2(v, user) for v in obj])
349 return None
352def register_config_error(self, exc):
353 try:
354 msg = getattr(exc, 'message', None) or str(exc.args[0])
355 except:
356 msg = repr(exc)
357 cei = Data(message=msg, stack=[])
358 for node in reversed(self.configStack):
359 cei.stack.append(
360 # @TODO actually this is a ConfigLocation object
361 Data(
362 objectUid=getattr(node, 'uid', ''),
363 objectType=getattr(node, 'extName', None) or class_name(node),
364 objectName=getattr(node, 'name', '') or getattr(node, 'title', ''),
365 propName='',
366 )
367 )
368 self.configErrors.append(cei)
371def super_invoke(node, method):
372 # since `super().configure` is mandatory in `configure` methods,
373 # let's automate this by collecting all super 'configure' methods
375 mro = []
377 for cls in type(node).mro():
378 try:
379 if method in vars(cls):
380 mro.append(cls)
381 except TypeError:
382 pass
384 for cls in reversed(mro):
385 getattr(cls, method)(node)
388def to_config(config, defaults):
389 return u.merge(Data(), defaults, config)