Coverage for gws-app/gws/base/action/manager.py: 53%
94 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""Action manager and helpers for CLI commands."""
3from typing import Optional
4import gws
5import gws.spec.runtime
8def get_action_for_cli(root: gws.Root, action_name: str, project_uid: Optional[str] = None) -> Optional[gws.Action]:
9 """Find an action for a CLI command, as the system user.
11 With a project uid, the action is searched in that project first. If it is not
12 found there, or no project uid is given, it is searched among the application
13 actions and then in all projects in turn. Errors are logged, not raised.
15 Args:
16 root: The configuration root.
17 action_name: Action type, for example ``alkis``.
18 project_uid: Optional project uid.
20 Returns:
21 The action object, or ``None`` if the project or the action is not found.
22 """
24 if project_uid:
25 project = root.app.project(project_uid)
26 if not project:
27 gws.log.error(f'project {project_uid!r} not found')
28 return None
29 act = root.app.actionMgr.find_action(project, action_name, root.app.authMgr.systemUser)
30 if act:
31 return act
32 gws.log.error(f'action {action_name!r} not found in {project_uid!r}')
34 act = root.app.actionMgr.find_action(None, action_name, root.app.authMgr.systemUser)
35 if act:
36 return act
38 for project in root.app.projects:
39 act = root.app.actionMgr.find_action(project, action_name, root.app.authMgr.systemUser)
40 if act:
41 gws.log.info(f'using action {action_name!r} from {project.uid!r}')
42 return act
44 gws.log.error(f'action {action_name!r} not found')
47def parse_cli_request(
48 root: gws.Root,
49 command_category: gws.CommandCategory,
50 command_name: str,
51 params: dict,
52 read_options=None,
53):
54 """Parse command parameters and return a command handler on a new action object.
56 The action class is instantiated directly, it is not a configured tree node.
58 Args:
59 root: The configuration root.
60 command_category: Command category.
61 command_name: Command name.
62 params: Raw command parameters.
63 read_options: Options for the spec reader.
65 Returns:
66 A tuple of the bound command method and the parsed request object.
68 Raises:
69 gws.NotFoundError: If the command is not found.
70 gws.BadRequestError: If the parameters cannot be parsed.
71 """
73 desc = root.specs.command_descriptor(command_category, command_name)
74 if not desc:
75 raise gws.NotFoundError(f'{command_category}.{command_name} not found')
77 try:
78 request = root.specs.read(params, desc.tArg, options=read_options)
79 except gws.spec.runtime.ReadError as exc:
80 raise gws.BadRequestError(f'read error: {exc!r}') from exc
82 cls = gws.u.require(root.specs.get_class(desc.tOwner))
83 action = cls()
85 fn = getattr(action, desc.methodName)
86 return fn, request
89class Object(gws.ActionManager):
90 """Action manager."""
92 def actions_for_project(self, project, user):
93 d = {}
95 for a in project.actions:
96 if user.can_use(a):
97 d[a.extType] = a
99 for a in self.root.app.actions:
100 if a.extType not in d and user.can_use(a):
101 d[a.extType] = a
103 return list(d.values())
105 def prepare_action(self, command_category, command_name, params, path, user, read_options=None):
106 desc = self.root.specs.command_descriptor(command_category, command_name)
107 if not desc:
108 raise gws.NotFoundError(f'{command_category}.{command_name} not found')
110 if desc.extCommandCategory == gws.CommandCategory.raw:
111 project_uid = params.get('projectUid')
113 if not project_uid:
114 path_parts = path.strip('/').split('/')
115 try:
116 project_uid = path_parts[path_parts.index('projectUid') + 1]
117 except (ValueError, IndexError):
118 pass
120 request = gws.Request(
121 projectUid=project_uid,
122 localeUid=params.get('localeUid'),
123 )
124 else:
125 try:
126 request = gws.u.require(self.root.specs.read(params, desc.tArg, options=read_options))
127 except gws.spec.runtime.ReadError as exc:
128 raise gws.BadRequestError(f'read error: {exc!r}') from exc
130 action = None
132 project_uid = request.get('projectUid')
133 if project_uid:
134 project = self.root.app.project(project_uid)
135 if not project:
136 raise gws.NotFoundError(f'project not found: {project_uid!r}')
137 if not user.can_use(project):
138 raise gws.ForbiddenError(f'project forbidden: {project_uid!r}')
139 action = self._find_by_ext_name(project, desc.owner.extName, user)
141 if not action:
142 action = self._find_by_ext_name(self.root.app, desc.owner.extName, user)
144 if not action:
145 raise gws.NotFoundError(f'action {desc.owner.extName!r}: not found')
147 fn = getattr(action, desc.methodName)
148 return fn, request
150 def find_action(self, project, ext_type, user):
151 if project:
152 a = self._find_by_ext_type(project, ext_type, user)
153 if a:
154 return a
155 return self._find_by_ext_type(self.root.app, ext_type, user)
157 # @TODO build indexes for this
159 def _find_by_ext_name(self, obj, ext_name: str, user: gws.User):
160 """Find an action of an object by its extension name, raise ``ForbiddenError`` if the user may not use it."""
161 for a in obj.actions:
162 if a.extName == ext_name:
163 if not user.can_use(a):
164 raise gws.ForbiddenError(f'action {ext_name!r}: forbidden in {obj!r}')
165 return a
167 def _find_by_ext_type(self, obj, ext_type: str, user: gws.User):
168 """Find an action of an object by its extension type, raise ``ForbiddenError`` if the user may not use it."""
169 for a in obj.actions:
170 if a.extType == ext_type:
171 if not user.can_use(a):
172 raise gws.ForbiddenError(f'action {ext_type!r}: forbidden in {obj!r}')
173 return a