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

1"""Internal implementations of ``gws.Node`` and ``gws.Root`` methods.""" 

2 

3from . import ( 

4 const as c, 

5 util as u, 

6 log, 

7) 

8 

9Access = None 

10Error = None 

11Data = None 

12Props = None 

13Object = None 

14 

15 

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)) + '>' 

25 

26 

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') 

32 

33 

34def node_create_child(self, classref, config, **kwargs): 

35 return self.root.create(classref, parent=self, config=config, **kwargs) 

36 

37 

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) 

42 

43 

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) 

48 

49 

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 

53 

54 

55def node_find_all(self, classref): 

56 return find_all_in(self.root, self.children, classref) 

57 

58 

59def node_find_first(self, classref): 

60 return find_first_in(self.root, self.children, classref) 

61 

62 

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 

71 

72 

73def node_find_ancestors(self, classref): 

74 ls = [] 

75 node = self.parent 

76 

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 

83 

84 return ls 

85 

86 

87def node_find_descendants(self, classref): 

88 ls = [] 

89 

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) 

95 

96 _walk(self) 

97 return ls 

98 

99 

100## 

101 

102 

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 

113 

114 

115def root_initialize(self, node, config): 

116 self.configStack.append(node) 

117 

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 

125 

126 self.configStack.pop() 

127 return ok 

128 

129 

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) 

142 

143 

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() 

149 

150 

151def root_find_all(self, classref): 

152 return find_all_in(self, self.nodes, classref) 

153 

154 

155def root_find_first(self, classref): 

156 return find_first_in(self, self.nodes, classref) 

157 

158 

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 

165 

166 

167def root_object_count(self) -> int: 

168 return len(self.nodes) 

169 

170 

171def root_create(self, classref, parent, config, **kwargs): 

172 config = to_config(config, kwargs) 

173 return create_node(self, classref, parent, config) 

174 

175 

176def root_create_shared(self, classref, config, **kwargs): 

177 config = to_config(config, kwargs) 

178 

179 uid = config.uid 

180 if not uid: 

181 config.uid = '_s_' + u.sha256([repr(classref), config]) 

182 

183 if config.uid in self.uidMap: 

184 return self.uidMap[config.uid] 

185 

186 return create_node(self, classref, None, config) 

187 

188 

189def root_create_temporary(self, classref, config, **kwargs): 

190 config = to_config(config, kwargs) 

191 return create_node(self, classref, None, config, temp=True) 

192 

193 

194def root_create_application(self, config, **kwargs): 

195 config = to_config(config, kwargs) 

196 

197 node = alloc_node(self, 'gws.base.application.core.Object') 

198 node.uid = c.APPLICATION_UID 

199 node.parent = self 

200 node.children = [] 

201 

202 self.nodes.append(node) 

203 self.uidMap[node.uid] = node 

204 self.app = node 

205 

206 self.initialize(node, config) 

207 

208 return node 

209 

210 

211## 

212 

213 

214def class_name(node): 

215 return node.__class__.__module__ + '.' + node.__class__.__name__ 

216 

217 

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') 

222 

223 node = cls() 

224 node.root = self 

225 node.extName = getattr(cls, 'extName', '') 

226 node.extType = getattr(cls, 'extType', '') 

227 

228 return node 

229 

230 

231def configure_permissions(self): 

232 perms = { 

233 Access.read: [], 

234 Access.write: [], 

235 Access.create: [], 

236 Access.delete: [], 

237 } 

238 

239 p = self.cfg('access') 

240 if p: 

241 perms[Access.read] = u.parse_acl(p) 

242 

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) 

250 

251 v = p.get('edit') 

252 if v: 

253 perms[Access.write] = perms[Access.create] = perms[Access.delete] = u.parse_acl(v) 

254 

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) 

259 

260 return perms 

261 

262 

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 = [] 

268 

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 

274 

275 if not temp: 

276 self.nodes.append(node) 

277 self.uidMap[node.uid] = node 

278 

279 if parent: 

280 parent.children.append(node) 

281 

282 return node 

283 

284 

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)] 

295 

296 

297def find_first_in(root, nodes, classref): 

298 found = find_all_in(root, nodes, classref) 

299 return found[0] if found else None 

300 

301 

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) 

307 

308 

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 

318 

319 

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') 

329 

330 

331def make_props2(obj, user): 

332 if u.is_atom(obj): 

333 return obj 

334 

335 if isinstance(obj, Object): 

336 if user.acl_bit(Access.read, obj) == c.DENY: 

337 return None 

338 obj = obj.props(user) 

339 

340 if isinstance(obj, Data): 

341 obj = vars(obj) 

342 

343 if u.is_dict(obj): 

344 return u.compact({k: make_props2(v, user) for k, v in obj.items()}) 

345 

346 if u.is_list(obj): 

347 return u.compact([make_props2(v, user) for v in obj]) 

348 

349 return None 

350 

351 

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) 

369 

370 

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 

374 

375 mro = [] 

376 

377 for cls in type(node).mro(): 

378 try: 

379 if method in vars(cls): 

380 mro.append(cls) 

381 except TypeError: 

382 pass 

383 

384 for cls in reversed(mro): 

385 getattr(cls, method)(node) 

386 

387 

388def to_config(config, defaults): 

389 return u.merge(Data(), defaults, config)