Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/modules/script.py: 0%

79 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-27 07:59 +0000

1import typing as t 

2from viur.core.bones import * 

3from viur.core.prototypes.tree import Tree, TreeSkel, SkelType 

4from viur.core.modules.file import File 

5from viur.core import db, conf, current, skeleton, tasks, errors 

6from viur.core.decorators import exposed 

7from viur.core.i18n import translate 

8 

9 

10class BaseScriptAbstractSkel(TreeSkel): 

11 

12 path = StringBone( 

13 descr="Path", 

14 readOnly=True, 

15 unique=UniqueValue(UniqueLockMethod.SameValue, True, "This path is already taken!") 

16 ) 

17 

18 @classmethod 

19 def fromClient(cls, skel, data, *args, **kwargs): 

20 # Set script name when provided, so that the path can be regenerated 

21 if name := data.get("name"): 

22 skel["name"] = name 

23 conf.main_app.script.update_path(skel) 

24 

25 ret = super().fromClient(skel, data, *args, **kwargs) 

26 

27 if not ret: 

28 # in case the path failed because the unique value is already taken, rewrite the error for name field 

29 for error in skel.errors: 

30 if error.severity == skeleton.ReadFromClientErrorSeverity.Invalid and error.fieldPath == ["path"]: 

31 error.fieldPath = ["name"] 

32 break 

33 

34 return ret 

35 

36 

37class ScriptNodeSkel(BaseScriptAbstractSkel): 

38 kindName = "viur-script-node" 

39 

40 rootNode = BooleanBone( 

41 descr="Is root node?", 

42 defaultValue=False, 

43 ) 

44 

45 plugin = BooleanBone( 

46 descr="Is plugin?", 

47 defaultValue=False 

48 ) 

49 

50 name = StringBone( 

51 descr="Folder", 

52 required=True, 

53 vfunc=lambda value: None if File.is_valid_filename(value) else "Foldername is invalid" 

54 ) 

55 

56 

57class ScriptLeafSkel(BaseScriptAbstractSkel): 

58 kindName = "viur-script-leaf" 

59 

60 name = StringBone( 

61 descr="Filename", 

62 required=True, 

63 vfunc=lambda value: 

64 None if File.is_valid_filename(value) and value.endswith(".py") and value.removesuffix(".py") 

65 else "Filename is invalid or doesn't have a '.py'-suffix", 

66 ) 

67 

68 script = RawBone( 

69 descr="Code", 

70 indexed=False, 

71 ) 

72 

73 access = SelectBone( 

74 descr="Required access rights to run this Script", 

75 values=lambda: { 

76 right: translate(f"viur.modules.user.accessright.{right}", defaultText=right) 

77 for right in sorted(conf.user.access_rights) 

78 }, 

79 multiple=True, 

80 ) 

81 

82 

83class Script(Tree): 

84 """ 

85 Script is a system module used to serve a filesystem for scripts used by ViUR Scriptor and ViUR CLI. 

86 """ 

87 

88 leafSkelCls = ScriptLeafSkel 

89 nodeSkelCls = ScriptNodeSkel 

90 

91 roles = { 

92 "admin": "*", 

93 } 

94 

95 def adminInfo(self): 

96 return conf.script_admin_info or {} 

97 

98 def getAvailableRootNodes(self): 

99 if not current.user.get(): 

100 return [] 

101 

102 return [{ 

103 "name": "Scripts", 

104 "key": self.rootnodeSkel(ensure=True)["key"], 

105 }] 

106 

107 @exposed 

108 def view(self, skelType: SkelType, key: db.Key | int | str, *args, **kwargs) -> t.Any: 

109 try: 

110 return super().view(skelType, key, *args, **kwargs) 

111 except errors.NotFound: 

112 # When key is not found, try to interpret key as path 

113 if skel := self.viewSkel(skelType).all().mergeExternalFilter({"path": key}).getSkel(): 

114 return super().view(skelType, skel["key"], *args, **kwargs) 

115 

116 raise 

117 

118 def onEdit(self, skelType, skel): 

119 self.update_path(skel) 

120 super().onEdit(skelType, skel) 

121 

122 def onEdited(self, skelType, skel): 

123 if skelType == "node": 

124 self.update_path_recursive("node", skel["path"], skel["key"]) 

125 self.update_path_recursive("leaf", skel["path"], skel["key"]) 

126 

127 super().onEdited(skelType, skel) 

128 

129 @tasks.CallDeferred 

130 def update_path_recursive(self, skel_type, path, parent_key, cursor=None): 

131 """ 

132 Recursively updates all items under a given parent key. 

133 """ 

134 query = self.editSkel(skel_type).all().filter("parententry", parent_key) 

135 query.setCursor(cursor) 

136 

137 for skel in query.fetch(99): 

138 new_path = path + "/" + skel["name"] 

139 

140 # only update when path changed 

141 if new_path != skel["path"]: 

142 skel["path"] = new_path # self.onEdit() is NOT required, as it resolves the path again. 

143 skel.write() 

144 self.onEdited(skel_type, skel) # triggers this recursion for nodes, again. 

145 

146 if cursor := query.getCursor(): 

147 self.update_path_recursive(skel_type, path, parent_key, cursor) 

148 

149 def update_path(self, skel): 

150 """ 

151 Updates the path-value of a either a folder or a script file, by resolving the repository's root node. 

152 """ 

153 path = [skel["name"]] 

154 

155 key = skel["parententry"] 

156 while key: 

157 parent_skel = self.viewSkel("node") 

158 if not parent_skel.read(key) or parent_skel["key"] == skel["parentrepo"]: 

159 break 

160 

161 path.insert(0, parent_skel["name"]) 

162 key = parent_skel["parententry"] 

163 

164 skel["path"] = "/".join(path)