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
« 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
10class BaseScriptAbstractSkel(TreeSkel):
12 path = StringBone(
13 descr="Path",
14 readOnly=True,
15 unique=UniqueValue(UniqueLockMethod.SameValue, True, "This path is already taken!")
16 )
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)
25 ret = super().fromClient(skel, data, *args, **kwargs)
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
34 return ret
37class ScriptNodeSkel(BaseScriptAbstractSkel):
38 kindName = "viur-script-node"
40 rootNode = BooleanBone(
41 descr="Is root node?",
42 defaultValue=False,
43 )
45 plugin = BooleanBone(
46 descr="Is plugin?",
47 defaultValue=False
48 )
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 )
57class ScriptLeafSkel(BaseScriptAbstractSkel):
58 kindName = "viur-script-leaf"
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 )
68 script = RawBone(
69 descr="Code",
70 indexed=False,
71 )
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 )
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 """
88 leafSkelCls = ScriptLeafSkel
89 nodeSkelCls = ScriptNodeSkel
91 roles = {
92 "admin": "*",
93 }
95 def adminInfo(self):
96 return conf.script_admin_info or {}
98 def getAvailableRootNodes(self):
99 if not current.user.get():
100 return []
102 return [{
103 "name": "Scripts",
104 "key": self.rootnodeSkel(ensure=True)["key"],
105 }]
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)
116 raise
118 def onEdit(self, skelType, skel):
119 self.update_path(skel)
120 super().onEdit(skelType, skel)
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"])
127 super().onEdited(skelType, skel)
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)
137 for skel in query.fetch(99):
138 new_path = path + "/" + skel["name"]
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.
146 if cursor := query.getCursor():
147 self.update_path_recursive(skel_type, path, parent_key, cursor)
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"]]
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
161 path.insert(0, parent_skel["name"])
162 key = parent_skel["parententry"]
164 skel["path"] = "/".join(path)