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.10, created at 2025-02-07 19:28 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-02-07 19:28 +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 [{"name": "Scripts", "key": self.ensureOwnModuleRootNode().key}]
104 @exposed
105 def view(self, skelType: SkelType, key: db.Key | int | str, *args, **kwargs) -> t.Any:
106 try:
107 return super().view(skelType, key, *args, **kwargs)
108 except errors.NotFound:
109 # When key is not found, try to interpret key as path
110 if skel := self.viewSkel(skelType).all().mergeExternalFilter({"path": key}).getSkel():
111 return super().view(skelType, skel["key"], *args, **kwargs)
113 raise
115 def onEdit(self, skelType, skel):
116 self.update_path(skel)
117 super().onEdit(skelType, skel)
119 def onEdited(self, skelType, skel):
120 if skelType == "node":
121 self.update_path_recursive("node", skel["path"], skel["key"])
122 self.update_path_recursive("leaf", skel["path"], skel["key"])
124 super().onEdited(skelType, skel)
126 @tasks.CallDeferred
127 def update_path_recursive(self, skel_type, path, parent_key, cursor=None):
128 """
129 Recursively updates all items under a given parent key.
130 """
131 query = self.editSkel(skel_type).all().filter("parententry", parent_key)
132 query.setCursor(cursor)
134 for skel in query.fetch(99):
135 new_path = path + "/" + skel["name"]
137 # only update when path changed
138 if new_path != skel["path"]:
139 skel["path"] = new_path # self.onEdit() is NOT required, as it resolves the path again.
140 skel.write()
141 self.onEdited(skel_type, skel) # triggers this recursion for nodes, again.
143 if cursor := query.getCursor():
144 self.update_path_recursive(skel_type, path, parent_key, cursor)
146 def update_path(self, skel):
147 """
148 Updates the path-value of a either a folder or a script file, by resolving the repository's root node.
149 """
150 path = [skel["name"]]
152 key = skel["parententry"]
153 while key:
154 parent_skel = self.viewSkel("node")
155 if not parent_skel.read(key) or parent_skel["key"] == skel["parentrepo"]:
156 break
158 path.insert(0, parent_skel["name"])
159 key = parent_skel["parententry"]
161 skel["path"] = "/".join(path)