strawberry-graphql 0.229.2__py3-none-any.whl → 0.229.2.dev1715873118__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
strawberry/permission.py CHANGED
@@ -8,12 +8,14 @@ from typing import (
8
8
  TYPE_CHECKING,
9
9
  Any,
10
10
  Awaitable,
11
- Dict,
12
11
  List,
12
+ Literal,
13
13
  Optional,
14
+ Tuple,
14
15
  Type,
15
16
  Union,
16
17
  )
18
+ from typing_extensions import deprecated
17
19
 
18
20
  from strawberry.exceptions import StrawberryGraphQLError
19
21
  from strawberry.exceptions.permission_fail_silently_requires_optional import (
@@ -51,17 +53,40 @@ class BasePermission(abc.ABC):
51
53
  @abc.abstractmethod
52
54
  def has_permission(
53
55
  self, source: Any, info: Info, **kwargs: Any
54
- ) -> Union[bool, Awaitable[bool]]:
56
+ ) -> Union[
57
+ bool,
58
+ Awaitable[bool],
59
+ Tuple[Literal[False], dict],
60
+ Awaitable[Tuple[Literal[False], dict]],
61
+ ]:
62
+ """
63
+ This method is a required override in the permission class. It checks if the user has the necessary permissions to access a specific field.
64
+
65
+ The method should return a boolean value:
66
+ - True: The user has the necessary permissions.
67
+ - False: The user does not have the necessary permissions. In this case, the `on_unauthorized` method will be invoked.
68
+
69
+ Avoid raising exceptions in this method. Instead, use the `on_unauthorized` method to handle errors and customize the error response.
70
+
71
+ If there's a need to pass additional information to the `on_unauthorized` method, return a tuple. The first element should be False, and the second element should be a dictionary containing the additional information.
72
+
73
+ Args:
74
+ source (Any): The source field that the permission check is being performed on.
75
+ info (Info): The GraphQL resolve info associated with the field.
76
+ **kwargs (Any): Additional arguments that are typically passed to the field resolver.
77
+
78
+ Returns:
79
+ bool or tuple: Returns True if the user has the necessary permissions. Returns False or a tuple (False, additional_info) if the user does not have the necessary permissions. In the latter case, the `on_unauthorized` method will be invoked.
80
+ """
55
81
  raise NotImplementedError(
56
82
  "Permission classes should override has_permission method"
57
83
  )
58
84
 
59
- def on_unauthorized(self) -> None:
85
+ def on_unauthorized(self, **kwargs: Any) -> None:
60
86
  """
61
87
  Default error raising for permissions.
62
88
  This can be overridden to customize the behavior.
63
89
  """
64
-
65
90
  # Instantiate error class
66
91
  error = self.error_class(self.message or "")
67
92
 
@@ -74,6 +99,9 @@ class BasePermission(abc.ABC):
74
99
  raise error
75
100
 
76
101
  @property
102
+ @deprecated(
103
+ "@schema_directive is deprecated and will be disabled by default on 31.12.2024 with future removal planned. Use the new @permissions directive instead."
104
+ )
77
105
  def schema_directive(self) -> object:
78
106
  if not self._schema_directive:
79
107
 
@@ -89,6 +117,93 @@ class BasePermission(abc.ABC):
89
117
 
90
118
  return self._schema_directive
91
119
 
120
+ @cached_property
121
+ def is_async(self) -> bool:
122
+ return iscoroutinefunction(self.has_permission)
123
+
124
+ def __and__(self, other: BasePermission):
125
+ return AndPermission([self, other])
126
+
127
+ def __or__(self, other: BasePermission):
128
+ return OrPermission([self, other])
129
+
130
+
131
+ class CompositePermission(BasePermission, abc.ABC):
132
+ def __init__(self, child_permissions: List[BasePermission]):
133
+ self.child_permissions = child_permissions
134
+
135
+ def on_unauthorized(self, **kwargs: Any) -> Any:
136
+ failed_permissions = kwargs.get("failed_permissions", [])
137
+ for permission in failed_permissions:
138
+ permission.on_unauthorized()
139
+
140
+ @cached_property
141
+ def is_async(self) -> bool:
142
+ return any(x.is_async for x in self.child_permissions)
143
+
144
+
145
+ class AndPermission(CompositePermission):
146
+ def has_permission(
147
+ self, source: Any, info: Info, **kwargs: Any
148
+ ) -> Union[
149
+ bool,
150
+ Awaitable[bool],
151
+ Tuple[Literal[False], dict],
152
+ Awaitable[Tuple[Literal[False], dict]],
153
+ ]:
154
+ if self.is_async:
155
+ return self._has_permission_async(source, info, **kwargs)
156
+
157
+ for permission in self.child_permissions:
158
+ if not permission.has_permission(source, info, **kwargs):
159
+ return False, {"failed_permissions": [permission]}
160
+ return True
161
+
162
+ async def _has_permission_async(
163
+ self, source: Any, info: Info, **kwargs: Any
164
+ ) -> Union[bool, Tuple[Literal[False], dict]]:
165
+ for permission in self.child_permissions:
166
+ if not await await_maybe(permission.has_permission(source, info, **kwargs)):
167
+ return False, {"failed_permissions": [permission]}
168
+ return True
169
+
170
+ def __and__(self, other: BasePermission):
171
+ return AndPermission([*self.child_permissions, other])
172
+
173
+
174
+ class OrPermission(CompositePermission):
175
+ def has_permission(
176
+ self, source: Any, info: Info, **kwargs: Any
177
+ ) -> Union[
178
+ bool,
179
+ Awaitable[bool],
180
+ Tuple[Literal[False], dict],
181
+ Awaitable[Tuple[Literal[False], dict]],
182
+ ]:
183
+ if self.is_async:
184
+ return self._has_permission_async(source, info, **kwargs)
185
+ failed_permissions = []
186
+ for permission in self.child_permissions:
187
+ if permission.has_permission(source, info, **kwargs):
188
+ return True
189
+ failed_permissions.append(permission)
190
+
191
+ return False, {"failed_permissions": failed_permissions}
192
+
193
+ async def _has_permission_async(
194
+ self, source: Any, info: Info, **kwargs: Any
195
+ ) -> Union[bool, Tuple[Literal[False], dict]]:
196
+ failed_permissions = []
197
+ for permission in self.child_permissions:
198
+ if await await_maybe(permission.has_permission(source, info, **kwargs)):
199
+ return True
200
+ failed_permissions.append(permission)
201
+
202
+ return False, {"failed_permissions": failed_permissions}
203
+
204
+ def __or__(self, other: BasePermission):
205
+ return OrPermission([*self.child_permissions, other])
206
+
92
207
 
93
208
  class PermissionExtension(FieldExtension):
94
209
  """
@@ -100,8 +215,8 @@ class PermissionExtension(FieldExtension):
100
215
 
101
216
  NOTE:
102
217
  Currently, this is automatically added to the field, when using
103
- field.permission_classes
104
- This is deprecated behavior, please manually add the extension to field.extensions
218
+ field.permission_classes. You are free to use whichever method you prefer.
219
+ Use PermissionExtension if you want additional customization.
105
220
  """
106
221
 
107
222
  def __init__(
@@ -117,12 +232,16 @@ class PermissionExtension(FieldExtension):
117
232
 
118
233
  def apply(self, field: StrawberryField) -> None:
119
234
  """
120
- Applies all of the permission directives to the schema
235
+ Applies all the permission directives to the schema
121
236
  and sets up silent permissions
122
237
  """
123
238
  if self.use_directives:
124
239
  field.directives.extend(
125
- p.schema_directive for p in self.permissions if p.schema_directive
240
+ [
241
+ p.schema_directive
242
+ for p in self.permissions
243
+ if not isinstance(p, CompositePermission)
244
+ ]
126
245
  )
127
246
  # We can only fail silently if the field is optional or a list
128
247
  if self.fail_silently:
@@ -132,28 +251,40 @@ class PermissionExtension(FieldExtension):
132
251
  elif isinstance(field.type, StrawberryList):
133
252
  self.return_empty_list = True
134
253
  else:
135
- errror = PermissionFailSilentlyRequiresOptionalError(field)
136
- raise errror
254
+ raise PermissionFailSilentlyRequiresOptionalError(field)
137
255
 
138
- def _on_unauthorized(self, permission: BasePermission) -> Any:
256
+ def _on_unauthorized(self, permission: BasePermission, **kwargs: Any) -> Any:
139
257
  if self.fail_silently:
140
258
  return [] if self.return_empty_list else None
141
- return permission.on_unauthorized()
259
+
260
+ if kwargs in (None, {}):
261
+ return permission.on_unauthorized()
262
+ return permission.on_unauthorized(**kwargs)
142
263
 
143
264
  def resolve(
144
265
  self,
145
266
  next_: SyncExtensionResolver,
146
267
  source: Any,
147
268
  info: Info,
148
- **kwargs: Dict[str, Any],
269
+ **kwargs: Any[str, Any],
149
270
  ) -> Any:
150
271
  """
151
272
  Checks if the permission should be accepted and
152
273
  raises an exception if not
153
274
  """
275
+
154
276
  for permission in self.permissions:
155
- if not permission.has_permission(source, info, **kwargs):
156
- return self._on_unauthorized(permission)
277
+ permission_response = permission.has_permission(source, info, **kwargs)
278
+
279
+ context = {}
280
+ if isinstance(permission_response, tuple):
281
+ has_permission, context = permission_response
282
+ else:
283
+ has_permission = permission_response
284
+
285
+ if not has_permission:
286
+ return self._on_unauthorized(permission, **context)
287
+
157
288
  return next_(source, info, **kwargs)
158
289
 
159
290
  async def resolve_async(
@@ -161,15 +292,21 @@ class PermissionExtension(FieldExtension):
161
292
  next_: AsyncExtensionResolver,
162
293
  source: Any,
163
294
  info: Info,
164
- **kwargs: Dict[str, Any],
295
+ **kwargs: Any[str, Any],
165
296
  ) -> Any:
166
297
  for permission in self.permissions:
167
- has_permission = await await_maybe(
298
+ permission_response = await await_maybe(
168
299
  permission.has_permission(source, info, **kwargs)
169
300
  )
170
301
 
302
+ context = {}
303
+ if isinstance(permission_response, tuple):
304
+ has_permission, context = permission_response
305
+ else:
306
+ has_permission = permission_response
307
+
171
308
  if not has_permission:
172
- return self._on_unauthorized(permission)
309
+ return self._on_unauthorized(permission, **context)
173
310
  next = next_(source, info, **kwargs)
174
311
  if inspect.isasyncgen(next):
175
312
  return next
@@ -179,9 +316,4 @@ class PermissionExtension(FieldExtension):
179
316
  def supports_sync(self) -> bool:
180
317
  """The Permission extension always supports async checking using await_maybe,
181
318
  but only supports sync checking if there are no async permissions"""
182
- async_permissions = [
183
- True
184
- for permission in self.permissions
185
- if iscoroutinefunction(permission.has_permission)
186
- ]
187
- return len(async_permissions) == 0
319
+ return all(not permission.is_async for permission in self.permissions)
@@ -302,7 +302,7 @@ def _get_namespace_from_ast(
302
302
  # here to resolve lazy types by execing the annotated args, resolving the
303
303
  # type directly and then adding it to extra namespace, so that _eval_type
304
304
  # can properly resolve it later
305
- type_name = args[0].strip(" '\"\n")
305
+ type_name = args[0].strip()
306
306
  for arg in args[1:]:
307
307
  evaled_arg = eval(arg, globalns, localns) # noqa: PGH001, S307
308
308
  if isinstance(evaled_arg, StrawberryLazyReference):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: strawberry-graphql
3
- Version: 0.229.2
3
+ Version: 0.229.2.dev1715873118
4
4
  Summary: A library for creating GraphQL APIs
5
5
  Home-page: https://strawberry.rocks/
6
6
  License: MIT
@@ -162,7 +162,7 @@ strawberry/litestar/handlers/graphql_ws_handler.py,sha256=vVpjd5rJOldF8aQWEGjmmN
162
162
  strawberry/mutation.py,sha256=NROPvHJU1BBxZB7Wj4Okxw4hDIYM59MCpukAGEmSNYA,255
163
163
  strawberry/object_type.py,sha256=iQL2NqO7I28bS0moWgxPmCDc0w1HewPcXq8Xyh-xAWI,12539
164
164
  strawberry/parent.py,sha256=rmedKjN4zdg4KTnNV8DENrzgNYVL67rXpHjHoBofMS4,825
165
- strawberry/permission.py,sha256=dcKx4Zlg4ZhcxEDBOSWzz0CUN4WPkcc_kJUVuvLLs6w,5925
165
+ strawberry/permission.py,sha256=blMwepQb20KLgnpNXWMcMZKa2soRo3zKjKXizPfIrIw,11238
166
166
  strawberry/printer/__init__.py,sha256=DmepjmgtkdF5RxK_7yC6qUyRWn56U-9qeZMbkztYB9w,62
167
167
  strawberry/printer/ast_from_value.py,sha256=MFIX2V51d9ocRvD0Njemjk8YIzKh2BB1g2iUcX6a3d8,4946
168
168
  strawberry/printer/printer.py,sha256=HUUecFETXWgfQtMzxjx9pNOXP3tDevqlU3sG7TC3AD8,17449
@@ -240,9 +240,9 @@ strawberry/utils/inspect.py,sha256=6z-tJpiWWm6E4-O6OUfhu689W9k1uY0m3FDwAfVCiNs,2
240
240
  strawberry/utils/logging.py,sha256=flS7hV0JiIOEdXcrIjda4WyIWix86cpHHFNJL8gl1y4,713
241
241
  strawberry/utils/operation.py,sha256=Um-tBCPl3_bVFN2Ph7o1mnrxfxBes4HFCj6T0x4kZxE,1135
242
242
  strawberry/utils/str_converters.py,sha256=avIgPVLg98vZH9mA2lhzVdyyjqzLsK2NdBw9mJQ02Xk,813
243
- strawberry/utils/typing.py,sha256=u9vUmf0F57s9OL_qBK4oLlQtkI__XuLRAQ8ryNBnPgQ,13491
244
- strawberry_graphql-0.229.2.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
245
- strawberry_graphql-0.229.2.dist-info/METADATA,sha256=9TO-t0AvKV4wkZcofWrX9JQuBjl9C7GIOpLzDiYgQrI,7821
246
- strawberry_graphql-0.229.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
247
- strawberry_graphql-0.229.2.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
248
- strawberry_graphql-0.229.2.dist-info/RECORD,,
243
+ strawberry/utils/typing.py,sha256=SQVOw1nuFZk2Pe3iz0o8ebzpoyvBVoGSQZVZj6-8k7I,13483
244
+ strawberry_graphql-0.229.2.dev1715873118.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
245
+ strawberry_graphql-0.229.2.dev1715873118.dist-info/METADATA,sha256=MSkHwXiY0s2dglAReE2EXuSKi9WQgZ9c6MI75-KeglE,7835
246
+ strawberry_graphql-0.229.2.dev1715873118.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
247
+ strawberry_graphql-0.229.2.dev1715873118.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
248
+ strawberry_graphql-0.229.2.dev1715873118.dist-info/RECORD,,