strawberry-graphql 0.238.1__tar.gz → 0.239.0__tar.gz

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.
Files changed (250) hide show
  1. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/PKG-INFO +1 -1
  2. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/pyproject.toml +7 -1
  3. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/aiohttp/views.py +35 -2
  4. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/asgi/__init__.py +24 -1
  5. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/channels/handlers/http_handler.py +49 -14
  6. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/django/views.py +32 -8
  7. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/fastapi/router.py +18 -0
  8. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/flask/views.py +8 -1
  9. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/http/async_base_view.py +118 -5
  10. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/http/base.py +11 -0
  11. strawberry_graphql-0.239.0/strawberry/http/parse_content_type.py +16 -0
  12. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/http/sync_base_view.py +13 -3
  13. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/litestar/controller.py +26 -1
  14. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/quart/views.py +17 -1
  15. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/sanic/views.py +32 -0
  16. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/schema/base.py +6 -2
  17. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/schema/execute.py +26 -13
  18. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/schema/schema.py +2 -2
  19. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/subscriptions/protocols/graphql_transport_ws/handlers.py +1 -1
  20. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/__init__.py +3 -1
  21. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/execution.py +17 -2
  22. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/graphql.py +5 -1
  23. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/LICENSE +0 -0
  24. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/README.md +0 -0
  25. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/__init__.py +0 -0
  26. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/__main__.py +0 -0
  27. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/aiohttp/__init__.py +0 -0
  28. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/aiohttp/handlers/__init__.py +0 -0
  29. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/aiohttp/handlers/graphql_transport_ws_handler.py +0 -0
  30. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/aiohttp/handlers/graphql_ws_handler.py +0 -0
  31. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/aiohttp/test/__init__.py +0 -0
  32. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/aiohttp/test/client.py +0 -0
  33. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/annotation.py +0 -0
  34. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/asgi/handlers/__init__.py +0 -0
  35. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/asgi/handlers/graphql_transport_ws_handler.py +0 -0
  36. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/asgi/handlers/graphql_ws_handler.py +0 -0
  37. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/asgi/test/__init__.py +0 -0
  38. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/asgi/test/client.py +0 -0
  39. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/chalice/__init__.py +0 -0
  40. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/chalice/views.py +0 -0
  41. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/channels/__init__.py +0 -0
  42. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/channels/handlers/__init__.py +0 -0
  43. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/channels/handlers/base.py +0 -0
  44. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/channels/handlers/graphql_transport_ws_handler.py +0 -0
  45. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/channels/handlers/graphql_ws_handler.py +0 -0
  46. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/channels/handlers/ws_handler.py +0 -0
  47. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/channels/router.py +0 -0
  48. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/channels/testing.py +0 -0
  49. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/cli/__init__.py +0 -0
  50. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/cli/app.py +0 -0
  51. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/cli/commands/__init__.py +0 -0
  52. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/cli/commands/codegen.py +0 -0
  53. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/cli/commands/export_schema.py +0 -0
  54. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/cli/commands/schema_codegen.py +0 -0
  55. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/cli/commands/server.py +0 -0
  56. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/cli/commands/upgrade/__init__.py +0 -0
  57. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/cli/commands/upgrade/_fake_progress.py +0 -0
  58. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/cli/commands/upgrade/_run_codemod.py +0 -0
  59. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/cli/constants.py +0 -0
  60. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/cli/debug_server.py +0 -0
  61. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/cli/utils/__init__.py +0 -0
  62. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/cli/utils/load_schema.py +0 -0
  63. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/codegen/__init__.py +0 -0
  64. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/codegen/exceptions.py +0 -0
  65. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/codegen/plugins/__init__.py +0 -0
  66. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/codegen/plugins/print_operation.py +0 -0
  67. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/codegen/plugins/python.py +0 -0
  68. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/codegen/plugins/typescript.py +0 -0
  69. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/codegen/query_codegen.py +0 -0
  70. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/codegen/types.py +0 -0
  71. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/codemods/__init__.py +0 -0
  72. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/codemods/annotated_unions.py +0 -0
  73. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/codemods/update_imports.py +0 -0
  74. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/dataloader.py +0 -0
  75. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/directive.py +0 -0
  76. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/django/__init__.py +0 -0
  77. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/django/apps.py +0 -0
  78. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/django/context.py +0 -0
  79. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/django/test/__init__.py +0 -0
  80. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/django/test/client.py +0 -0
  81. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/__init__.py +0 -0
  82. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/conflicting_arguments.py +0 -0
  83. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/duplicated_type_name.py +0 -0
  84. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/exception.py +0 -0
  85. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/exception_source.py +0 -0
  86. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/handler.py +0 -0
  87. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/invalid_argument_type.py +0 -0
  88. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/invalid_union_type.py +0 -0
  89. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/missing_arguments_annotations.py +0 -0
  90. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/missing_dependencies.py +0 -0
  91. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/missing_field_annotation.py +0 -0
  92. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/missing_return_annotation.py +0 -0
  93. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/not_a_strawberry_enum.py +0 -0
  94. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/object_is_not_a_class.py +0 -0
  95. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/object_is_not_an_enum.py +0 -0
  96. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/permission_fail_silently_requires_optional.py +0 -0
  97. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/private_strawberry_field.py +0 -0
  98. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/scalar_already_registered.py +0 -0
  99. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/syntax.py +0 -0
  100. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/unresolved_field_type.py +0 -0
  101. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/utils/__init__.py +0 -0
  102. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/exceptions/utils/source_finder.py +0 -0
  103. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/experimental/__init__.py +0 -0
  104. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/experimental/pydantic/__init__.py +0 -0
  105. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/experimental/pydantic/_compat.py +0 -0
  106. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/experimental/pydantic/conversion.py +0 -0
  107. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/experimental/pydantic/conversion_types.py +0 -0
  108. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/experimental/pydantic/error_type.py +0 -0
  109. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/experimental/pydantic/exceptions.py +0 -0
  110. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/experimental/pydantic/fields.py +0 -0
  111. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/experimental/pydantic/object_type.py +0 -0
  112. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/experimental/pydantic/utils.py +0 -0
  113. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/ext/LICENSE +0 -0
  114. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/ext/__init__.py +0 -0
  115. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/ext/dataclasses/LICENSE +0 -0
  116. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/ext/dataclasses/__init__.py +0 -0
  117. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/ext/dataclasses/dataclasses.py +0 -0
  118. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/ext/mypy_plugin.py +0 -0
  119. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/__init__.py +0 -0
  120. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/add_validation_rules.py +0 -0
  121. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/base_extension.py +0 -0
  122. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/context.py +0 -0
  123. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/directives.py +0 -0
  124. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/disable_validation.py +0 -0
  125. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/field_extension.py +0 -0
  126. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/mask_errors.py +0 -0
  127. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/max_aliases.py +0 -0
  128. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/max_tokens.py +0 -0
  129. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/parser_cache.py +0 -0
  130. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/pyinstrument.py +0 -0
  131. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/query_depth_limiter.py +0 -0
  132. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/runner.py +0 -0
  133. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/tracing/__init__.py +0 -0
  134. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/tracing/apollo.py +0 -0
  135. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/tracing/datadog.py +0 -0
  136. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/tracing/opentelemetry.py +0 -0
  137. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/tracing/sentry.py +0 -0
  138. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/tracing/utils.py +0 -0
  139. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/utils.py +0 -0
  140. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/extensions/validation_cache.py +0 -0
  141. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/fastapi/__init__.py +0 -0
  142. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/fastapi/context.py +0 -0
  143. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/fastapi/handlers/__init__.py +0 -0
  144. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/fastapi/handlers/graphql_transport_ws_handler.py +0 -0
  145. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/fastapi/handlers/graphql_ws_handler.py +0 -0
  146. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/federation/__init__.py +0 -0
  147. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/federation/argument.py +0 -0
  148. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/federation/enum.py +0 -0
  149. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/federation/field.py +0 -0
  150. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/federation/mutation.py +0 -0
  151. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/federation/object_type.py +0 -0
  152. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/federation/scalar.py +0 -0
  153. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/federation/schema.py +0 -0
  154. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/federation/schema_directive.py +0 -0
  155. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/federation/schema_directives.py +0 -0
  156. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/federation/types.py +0 -0
  157. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/federation/union.py +0 -0
  158. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/field_extensions/__init__.py +0 -0
  159. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/field_extensions/input_mutation.py +0 -0
  160. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/file_uploads/__init__.py +0 -0
  161. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/file_uploads/scalars.py +0 -0
  162. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/file_uploads/utils.py +0 -0
  163. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/flask/__init__.py +0 -0
  164. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/http/__init__.py +0 -0
  165. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/http/exceptions.py +0 -0
  166. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/http/ides.py +0 -0
  167. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/http/temporal_response.py +0 -0
  168. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/http/types.py +0 -0
  169. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/http/typevars.py +0 -0
  170. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/litestar/__init__.py +0 -0
  171. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/litestar/handlers/__init__.py +0 -0
  172. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/litestar/handlers/graphql_transport_ws_handler.py +0 -0
  173. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/litestar/handlers/graphql_ws_handler.py +0 -0
  174. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/parent.py +0 -0
  175. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/permission.py +0 -0
  176. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/printer/__init__.py +0 -0
  177. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/printer/ast_from_value.py +0 -0
  178. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/printer/printer.py +0 -0
  179. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/py.typed +0 -0
  180. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/quart/__init__.py +0 -0
  181. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/relay/__init__.py +0 -0
  182. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/relay/exceptions.py +0 -0
  183. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/relay/fields.py +0 -0
  184. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/relay/types.py +0 -0
  185. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/relay/utils.py +0 -0
  186. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/resolvers.py +0 -0
  187. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/sanic/__init__.py +0 -0
  188. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/sanic/context.py +0 -0
  189. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/sanic/utils.py +0 -0
  190. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/scalars.py +0 -0
  191. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/schema/__init__.py +0 -0
  192. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/schema/compat.py +0 -0
  193. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/schema/config.py +0 -0
  194. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/schema/exceptions.py +0 -0
  195. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/schema/name_converter.py +0 -0
  196. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/schema/schema_converter.py +0 -0
  197. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/schema/types/__init__.py +0 -0
  198. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/schema/types/base_scalars.py +0 -0
  199. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/schema/types/concrete_type.py +0 -0
  200. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/schema/types/scalar.py +0 -0
  201. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/schema/validation_rules/__init__.py +0 -0
  202. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/schema/validation_rules/one_of.py +0 -0
  203. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/schema_codegen/__init__.py +0 -0
  204. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/schema_directive.py +0 -0
  205. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/schema_directives.py +0 -0
  206. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/static/apollo-sandbox.html +0 -0
  207. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/static/graphiql.html +0 -0
  208. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/static/pathfinder.html +0 -0
  209. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/subscriptions/__init__.py +0 -0
  210. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/subscriptions/protocols/__init__.py +0 -0
  211. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/subscriptions/protocols/graphql_transport_ws/__init__.py +0 -0
  212. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/subscriptions/protocols/graphql_transport_ws/types.py +0 -0
  213. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/subscriptions/protocols/graphql_ws/__init__.py +0 -0
  214. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/subscriptions/protocols/graphql_ws/handlers.py +0 -0
  215. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/subscriptions/protocols/graphql_ws/types.py +0 -0
  216. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/test/__init__.py +0 -0
  217. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/test/client.py +0 -0
  218. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/tools/__init__.py +0 -0
  219. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/tools/create_type.py +0 -0
  220. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/tools/merge_types.py +0 -0
  221. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/arguments.py +0 -0
  222. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/auto.py +0 -0
  223. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/base.py +0 -0
  224. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/enum.py +0 -0
  225. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/field.py +0 -0
  226. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/fields/__init__.py +0 -0
  227. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/fields/resolver.py +0 -0
  228. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/info.py +0 -0
  229. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/lazy_type.py +0 -0
  230. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/mutation.py +0 -0
  231. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/nodes.py +0 -0
  232. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/object_type.py +0 -0
  233. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/private.py +0 -0
  234. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/scalar.py +0 -0
  235. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/type_resolver.py +0 -0
  236. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/union.py +0 -0
  237. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/types/unset.py +0 -0
  238. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/utils/__init__.py +0 -0
  239. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/utils/aio.py +0 -0
  240. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/utils/await_maybe.py +0 -0
  241. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/utils/dataclasses.py +0 -0
  242. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/utils/debug.py +0 -0
  243. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/utils/deprecations.py +0 -0
  244. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/utils/graphql_lexer.py +0 -0
  245. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/utils/importer.py +0 -0
  246. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/utils/inspect.py +0 -0
  247. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/utils/logging.py +0 -0
  248. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/utils/operation.py +0 -0
  249. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/utils/str_converters.py +0 -0
  250. {strawberry_graphql-0.238.1 → strawberry_graphql-0.239.0}/strawberry/utils/typing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: strawberry-graphql
3
- Version: 0.238.1
3
+ Version: 0.239.0
4
4
  Summary: A library for creating GraphQL APIs
5
5
  Home-page: https://strawberry.rocks/
6
6
  License: MIT
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "strawberry-graphql"
3
3
  packages = [ { include = "strawberry" } ]
4
- version = "0.238.1"
4
+ version = "0.239.0"
5
5
  description = "A library for creating GraphQL APIs"
6
6
  authors = ["Patrick Arminio <patrick.arminio@gmail.com>"]
7
7
  license = "MIT"
@@ -173,6 +173,12 @@ filterwarnings = [
173
173
  # ignoring the text instead of the whole warning because we'd
174
174
  # get an error when django is not installed
175
175
  "ignore:The default value of USE_TZ",
176
+ "ignore::DeprecationWarning:pydantic_openapi_schema.*",
177
+ "ignore::DeprecationWarning:graphql.*",
178
+ "ignore::DeprecationWarning:websockets.*",
179
+ "ignore::DeprecationWarning:pydantic.*",
180
+ "ignore::UserWarning:pydantic.*",
181
+ "ignore::DeprecationWarning:pkg_resources.*",
176
182
  ]
177
183
 
178
184
  [tool.autopub]
@@ -7,10 +7,13 @@ from io import BytesIO
7
7
  from typing import (
8
8
  TYPE_CHECKING,
9
9
  Any,
10
+ AsyncGenerator,
11
+ Callable,
10
12
  Dict,
11
13
  Iterable,
12
14
  Mapping,
13
15
  Optional,
16
+ Union,
14
17
  cast,
15
18
  )
16
19
 
@@ -73,11 +76,17 @@ class AioHTTPRequestAdapter(AsyncHTTPRequestAdapter):
73
76
 
74
77
  @property
75
78
  def content_type(self) -> Optional[str]:
76
- return self.request.content_type
79
+ return self.headers.get("content-type")
77
80
 
78
81
 
79
82
  class GraphQLView(
80
- AsyncBaseHTTPView[web.Request, web.Response, web.Response, Context, RootValue]
83
+ AsyncBaseHTTPView[
84
+ web.Request,
85
+ Union[web.Response, web.StreamResponse],
86
+ web.Response,
87
+ Context,
88
+ RootValue,
89
+ ]
81
90
  ):
82
91
  # Mark the view as coroutine so that AIOHTTP does not confuse it with a deprecated
83
92
  # bare handler function.
@@ -180,5 +189,29 @@ class GraphQLView(
180
189
 
181
190
  return sub_response
182
191
 
192
+ async def create_multipart_response(
193
+ self,
194
+ request: web.Request,
195
+ stream: Callable[[], AsyncGenerator[str, None]],
196
+ sub_response: web.Response,
197
+ ) -> web.StreamResponse:
198
+ response = web.StreamResponse(
199
+ status=sub_response.status,
200
+ headers={
201
+ **sub_response.headers,
202
+ "Transfer-Encoding": "chunked",
203
+ "Content-type": "multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json",
204
+ },
205
+ )
206
+
207
+ await response.prepare(request)
208
+
209
+ async for data in stream():
210
+ await response.write(data.encode())
211
+
212
+ await response.write_eof()
213
+
214
+ return response
215
+
183
216
 
184
217
  __all__ = ["GraphQLView"]
@@ -5,6 +5,8 @@ from datetime import timedelta
5
5
  from typing import (
6
6
  TYPE_CHECKING,
7
7
  Any,
8
+ AsyncIterator,
9
+ Callable,
8
10
  Mapping,
9
11
  Optional,
10
12
  Sequence,
@@ -14,7 +16,12 @@ from typing import (
14
16
 
15
17
  from starlette import status
16
18
  from starlette.requests import Request
17
- from starlette.responses import HTMLResponse, PlainTextResponse, Response
19
+ from starlette.responses import (
20
+ HTMLResponse,
21
+ PlainTextResponse,
22
+ Response,
23
+ StreamingResponse,
24
+ )
18
25
  from starlette.websockets import WebSocket
19
26
 
20
27
  from strawberry.asgi.handlers import (
@@ -213,3 +220,19 @@ class GraphQL(
213
220
  response.status_code = sub_response.status_code
214
221
 
215
222
  return response
223
+
224
+ async def create_multipart_response(
225
+ self,
226
+ request: Request | WebSocket,
227
+ stream: Callable[[], AsyncIterator[str]],
228
+ sub_response: Response,
229
+ ) -> Response:
230
+ return StreamingResponse(
231
+ stream(),
232
+ status_code=sub_response.status_code,
233
+ headers={
234
+ **sub_response.headers,
235
+ "Transfer-Encoding": "chunked",
236
+ "Content-type": "multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json",
237
+ },
238
+ )
@@ -1,8 +1,3 @@
1
- """GraphQLHTTPHandler.
2
-
3
- A consumer to provide a graphql endpoint, and optionally graphiql.
4
- """
5
-
6
1
  from __future__ import annotations
7
2
 
8
3
  import dataclasses
@@ -10,7 +5,17 @@ import json
10
5
  import warnings
11
6
  from functools import cached_property
12
7
  from io import BytesIO
13
- from typing import TYPE_CHECKING, Any, Dict, Mapping, Optional, Union
8
+ from typing import (
9
+ TYPE_CHECKING,
10
+ Any,
11
+ AsyncGenerator,
12
+ Callable,
13
+ Dict,
14
+ Mapping,
15
+ Optional,
16
+ Union,
17
+ )
18
+ from typing_extensions import assert_never
14
19
  from urllib.parse import parse_qs
15
20
 
16
21
  from django.conf import settings
@@ -44,6 +49,14 @@ class ChannelsResponse:
44
49
  headers: Dict[bytes, bytes] = dataclasses.field(default_factory=dict)
45
50
 
46
51
 
52
+ @dataclasses.dataclass
53
+ class MultipartChannelsResponse:
54
+ stream: Callable[[], AsyncGenerator[str, None]]
55
+ status: int = 200
56
+ content_type: str = "multipart/mixed;boundary=graphql;subscriptionSpec=1.0"
57
+ headers: Dict[bytes, bytes] = dataclasses.field(default_factory=dict)
58
+
59
+
47
60
  @dataclasses.dataclass
48
61
  class ChannelsRequest:
49
62
  consumer: ChannelsConsumer
@@ -186,16 +199,28 @@ class BaseGraphQLHTTPConsumer(ChannelsConsumer, AsyncHttpConsumer):
186
199
  async def handle(self, body: bytes) -> None:
187
200
  request = ChannelsRequest(consumer=self, body=body)
188
201
  try:
189
- response: ChannelsResponse = await self.run(request)
202
+ response = await self.run(request)
190
203
 
191
204
  if b"Content-Type" not in response.headers:
192
205
  response.headers[b"Content-Type"] = response.content_type.encode()
193
206
 
194
- await self.send_response(
195
- response.status,
196
- response.content,
197
- headers=response.headers,
198
- )
207
+ if isinstance(response, MultipartChannelsResponse):
208
+ response.headers[b"Transfer-Encoding"] = b"chunked"
209
+ await self.send_headers(headers=response.headers)
210
+
211
+ async for chunk in response.stream():
212
+ await self.send_body(chunk.encode("utf-8"), more_body=True)
213
+
214
+ await self.send_body(b"", more_body=False)
215
+
216
+ elif isinstance(response, ChannelsResponse):
217
+ await self.send_response(
218
+ response.status,
219
+ response.content,
220
+ headers=response.headers,
221
+ )
222
+ else:
223
+ assert_never(response)
199
224
  except HTTPException as e:
200
225
  await self.send_response(e.status_code, e.reason.encode())
201
226
 
@@ -204,7 +229,7 @@ class GraphQLHTTPConsumer(
204
229
  BaseGraphQLHTTPConsumer,
205
230
  AsyncBaseHTTPView[
206
231
  ChannelsRequest,
207
- ChannelsResponse,
232
+ Union[ChannelsResponse, MultipartChannelsResponse],
208
233
  TemporalResponse,
209
234
  Context,
210
235
  RootValue,
@@ -248,6 +273,16 @@ class GraphQLHTTPConsumer(
248
273
  async def get_sub_response(self, request: ChannelsRequest) -> TemporalResponse:
249
274
  return TemporalResponse()
250
275
 
276
+ async def create_multipart_response(
277
+ self,
278
+ request: ChannelsRequest,
279
+ stream: Callable[[], AsyncGenerator[str, None]],
280
+ sub_response: TemporalResponse,
281
+ ) -> MultipartChannelsResponse:
282
+ status = sub_response.status_code or 200
283
+ headers = {k.encode(): v.encode() for k, v in sub_response.headers.items()}
284
+ return MultipartChannelsResponse(stream=stream, status=status, headers=headers)
285
+
251
286
  async def render_graphql_ide(self, request: ChannelsRequest) -> ChannelsResponse:
252
287
  return ChannelsResponse(
253
288
  content=self.graphql_ide_html.encode(), content_type="text/html"
@@ -302,7 +337,7 @@ class SyncGraphQLHTTPConsumer(
302
337
  request: ChannelsRequest,
303
338
  context: Optional[Context] = UNSET,
304
339
  root_value: Optional[RootValue] = UNSET,
305
- ) -> ChannelsResponse:
340
+ ) -> ChannelsResponse | MultipartChannelsResponse:
306
341
  return super().run(request, context, root_value)
307
342
 
308
343
 
@@ -5,6 +5,7 @@ import warnings
5
5
  from typing import (
6
6
  TYPE_CHECKING,
7
7
  Any,
8
+ AsyncIterator,
8
9
  Callable,
9
10
  Mapping,
10
11
  Optional,
@@ -14,8 +15,14 @@ from typing import (
14
15
 
15
16
  from asgiref.sync import markcoroutinefunction
16
17
  from django.core.serializers.json import DjangoJSONEncoder
17
- from django.http import HttpRequest, HttpResponseNotAllowed, JsonResponse
18
- from django.http.response import HttpResponse
18
+ from django.http import (
19
+ HttpRequest,
20
+ HttpResponse,
21
+ HttpResponseNotAllowed,
22
+ JsonResponse,
23
+ StreamingHttpResponse,
24
+ )
25
+ from django.http.response import HttpResponseBase
19
26
  from django.template import RequestContext, Template
20
27
  from django.template.exceptions import TemplateDoesNotExist
21
28
  from django.template.loader import render_to_string
@@ -116,7 +123,7 @@ class AsyncDjangoHTTPRequestAdapter(AsyncHTTPRequestAdapter):
116
123
 
117
124
  @property
118
125
  def content_type(self) -> Optional[str]:
119
- return self.request.content_type
126
+ return self.headers.get("Content-type")
120
127
 
121
128
  async def get_body(self) -> str:
122
129
  return self.request.body.decode()
@@ -159,8 +166,9 @@ class BaseView:
159
166
 
160
167
  def create_response(
161
168
  self, response_data: GraphQLHTTPResponse, sub_response: HttpResponse
162
- ) -> HttpResponse:
169
+ ) -> HttpResponseBase:
163
170
  data = self.encode_json(response_data)
171
+
164
172
  response = HttpResponse(
165
173
  data,
166
174
  content_type="application/json",
@@ -177,6 +185,22 @@ class BaseView:
177
185
 
178
186
  return response
179
187
 
188
+ async def create_multipart_response(
189
+ self,
190
+ request: HttpRequest,
191
+ stream: Callable[[], AsyncIterator[Any]],
192
+ sub_response: TemporalHttpResponse,
193
+ ) -> HttpResponseBase:
194
+ return StreamingHttpResponse(
195
+ streaming_content=stream(),
196
+ status=sub_response.status_code,
197
+ headers={
198
+ **sub_response.headers,
199
+ "Transfer-Encoding": "chunked",
200
+ "Content-type": "multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json",
201
+ },
202
+ )
203
+
180
204
  def encode_json(self, response_data: GraphQLHTTPResponse) -> str:
181
205
  return json.dumps(response_data, cls=DjangoJSONEncoder)
182
206
 
@@ -184,7 +208,7 @@ class BaseView:
184
208
  class GraphQLView(
185
209
  BaseView,
186
210
  SyncBaseHTTPView[
187
- HttpRequest, HttpResponse, TemporalHttpResponse, Context, RootValue
211
+ HttpRequest, HttpResponseBase, TemporalHttpResponse, Context, RootValue
188
212
  ],
189
213
  View,
190
214
  ):
@@ -207,7 +231,7 @@ class GraphQLView(
207
231
  @method_decorator(csrf_exempt)
208
232
  def dispatch(
209
233
  self, request: HttpRequest, *args: Any, **kwargs: Any
210
- ) -> Union[HttpResponseNotAllowed, TemplateResponse, HttpResponse]:
234
+ ) -> Union[HttpResponseNotAllowed, TemplateResponse, HttpResponseBase]:
211
235
  try:
212
236
  return self.run(request=request)
213
237
  except HTTPException as e:
@@ -233,7 +257,7 @@ class GraphQLView(
233
257
  class AsyncGraphQLView(
234
258
  BaseView,
235
259
  AsyncBaseHTTPView[
236
- HttpRequest, HttpResponse, TemporalHttpResponse, Context, RootValue
260
+ HttpRequest, HttpResponseBase, TemporalHttpResponse, Context, RootValue
237
261
  ],
238
262
  View,
239
263
  ):
@@ -266,7 +290,7 @@ class AsyncGraphQLView(
266
290
  @method_decorator(csrf_exempt)
267
291
  async def dispatch( # pyright: ignore
268
292
  self, request: HttpRequest, *args: Any, **kwargs: Any
269
- ) -> Union[HttpResponseNotAllowed, TemplateResponse, HttpResponse]:
293
+ ) -> Union[HttpResponseNotAllowed, TemplateResponse, HttpResponseBase]:
270
294
  try:
271
295
  return await self.run(request=request)
272
296
  except HTTPException as e:
@@ -6,6 +6,7 @@ from inspect import signature
6
6
  from typing import (
7
7
  TYPE_CHECKING,
8
8
  Any,
9
+ AsyncIterator,
9
10
  Awaitable,
10
11
  Callable,
11
12
  Dict,
@@ -25,6 +26,7 @@ from starlette.responses import (
25
26
  JSONResponse,
26
27
  PlainTextResponse,
27
28
  Response,
29
+ StreamingResponse,
28
30
  )
29
31
  from starlette.websockets import WebSocket
30
32
 
@@ -330,5 +332,21 @@ class GraphQLRouter(
330
332
 
331
333
  return response
332
334
 
335
+ async def create_multipart_response(
336
+ self,
337
+ request: Request,
338
+ stream: Callable[[], AsyncIterator[str]],
339
+ sub_response: Response,
340
+ ) -> Response:
341
+ return StreamingResponse(
342
+ stream(),
343
+ status_code=sub_response.status_code,
344
+ headers={
345
+ **sub_response.headers,
346
+ "Transfer-Encoding": "chunked",
347
+ "Content-type": "multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json",
348
+ },
349
+ )
350
+
333
351
 
334
352
  __all__ = ["GraphQLRouter"]
@@ -1,7 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import warnings
4
- from typing import TYPE_CHECKING, Any, Mapping, Optional, Union, cast
4
+ from typing import (
5
+ TYPE_CHECKING,
6
+ Any,
7
+ Mapping,
8
+ Optional,
9
+ Union,
10
+ cast,
11
+ )
5
12
 
6
13
  from flask import Request, Response, render_template_string, request
7
14
  from flask.views import View
@@ -1,12 +1,17 @@
1
1
  import abc
2
+ import asyncio
3
+ import contextlib
2
4
  import json
3
5
  from typing import (
6
+ Any,
7
+ AsyncGenerator,
4
8
  Callable,
5
9
  Dict,
6
10
  Generic,
7
11
  List,
8
12
  Mapping,
9
13
  Optional,
14
+ Tuple,
10
15
  Union,
11
16
  )
12
17
 
@@ -15,15 +20,20 @@ from graphql import GraphQLError
15
20
  from strawberry import UNSET
16
21
  from strawberry.exceptions import MissingQueryError
17
22
  from strawberry.file_uploads.utils import replace_placeholders_with_files
18
- from strawberry.http import GraphQLHTTPResponse, GraphQLRequestData, process_result
23
+ from strawberry.http import (
24
+ GraphQLHTTPResponse,
25
+ GraphQLRequestData,
26
+ process_result,
27
+ )
19
28
  from strawberry.http.ides import GraphQL_IDE
20
29
  from strawberry.schema.base import BaseSchema
21
30
  from strawberry.schema.exceptions import InvalidOperationTypeError
22
- from strawberry.types import ExecutionResult
31
+ from strawberry.types import ExecutionResult, SubscriptionExecutionResult
23
32
  from strawberry.types.graphql import OperationType
24
33
 
25
34
  from .base import BaseView
26
35
  from .exceptions import HTTPException
36
+ from .parse_content_type import parse_content_type
27
37
  from .types import FormData, HTTPMethod, QueryParams
28
38
  from .typevars import Context, Request, Response, RootValue, SubResponse
29
39
 
@@ -82,9 +92,17 @@ class AsyncBaseHTTPView(
82
92
  @abc.abstractmethod
83
93
  async def render_graphql_ide(self, request: Request) -> Response: ...
84
94
 
95
+ async def create_multipart_response(
96
+ self,
97
+ request: Request,
98
+ stream: Callable[[], AsyncGenerator[str, None]],
99
+ sub_response: SubResponse,
100
+ ) -> Response:
101
+ raise ValueError("Multipart responses are not supported")
102
+
85
103
  async def execute_operation(
86
104
  self, request: Request, context: Context, root_value: Optional[RootValue]
87
- ) -> ExecutionResult:
105
+ ) -> Union[ExecutionResult, SubscriptionExecutionResult]:
88
106
  request_adapter = self.request_adapter_class(request)
89
107
 
90
108
  try:
@@ -178,6 +196,11 @@ class AsyncBaseHTTPView(
178
196
  except MissingQueryError as e:
179
197
  raise HTTPException(400, "No GraphQL query found in the request") from e
180
198
 
199
+ if isinstance(result, SubscriptionExecutionResult):
200
+ stream = self._get_stream(request, result)
201
+
202
+ return await self.create_multipart_response(request, stream, sub_response)
203
+
181
204
  response_data = await self.process_result(request=request, result=result)
182
205
 
183
206
  if result.errors:
@@ -187,17 +210,107 @@ class AsyncBaseHTTPView(
187
210
  response_data=response_data, sub_response=sub_response
188
211
  )
189
212
 
213
+ def encode_multipart_data(self, data: Any, separator: str) -> str:
214
+ return "".join(
215
+ [
216
+ f"\r\n--{separator}\r\n",
217
+ "Content-Type: application/json\r\n\r\n",
218
+ self.encode_json(data),
219
+ "\n",
220
+ ]
221
+ )
222
+
223
+ def _stream_with_heartbeat(
224
+ self, stream: Callable[[], AsyncGenerator[str, None]]
225
+ ) -> Callable[[], AsyncGenerator[str, None]]:
226
+ """Adds a heartbeat to the stream, to prevent the connection from closing when there are no messages being sent."""
227
+ queue = asyncio.Queue[Tuple[bool, Any]](1)
228
+
229
+ cancelling = False
230
+
231
+ async def drain() -> None:
232
+ try:
233
+ async for item in stream():
234
+ await queue.put((False, item))
235
+ except Exception as e:
236
+ if not cancelling:
237
+ await queue.put((True, e))
238
+ else:
239
+ raise
240
+
241
+ async def heartbeat() -> None:
242
+ while True:
243
+ await queue.put((False, self.encode_multipart_data({}, "graphql")))
244
+
245
+ await asyncio.sleep(5)
246
+
247
+ async def merged() -> AsyncGenerator[str, None]:
248
+ heartbeat_task = asyncio.create_task(heartbeat())
249
+ task = asyncio.create_task(drain())
250
+
251
+ async def cancel_tasks() -> None:
252
+ nonlocal cancelling
253
+ cancelling = True
254
+ task.cancel()
255
+
256
+ with contextlib.suppress(asyncio.CancelledError):
257
+ await task
258
+
259
+ heartbeat_task.cancel()
260
+
261
+ with contextlib.suppress(asyncio.CancelledError):
262
+ await heartbeat_task
263
+
264
+ try:
265
+ while not task.done():
266
+ raised, data = await queue.get()
267
+
268
+ if raised:
269
+ await cancel_tasks()
270
+ raise data
271
+
272
+ yield data
273
+ finally:
274
+ await cancel_tasks()
275
+
276
+ return merged
277
+
278
+ def _get_stream(
279
+ self,
280
+ request: Request,
281
+ result: SubscriptionExecutionResult,
282
+ separator: str = "graphql",
283
+ ) -> Callable[[], AsyncGenerator[str, None]]:
284
+ async def stream() -> AsyncGenerator[str, None]:
285
+ async for value in result:
286
+ response = await self.process_result(request, value)
287
+ yield self.encode_multipart_data({"payload": response}, separator)
288
+
289
+ yield f"\r\n--{separator}--\r\n"
290
+
291
+ return self._stream_with_heartbeat(stream)
292
+
293
+ async def parse_multipart_subscriptions(
294
+ self, request: AsyncHTTPRequestAdapter
295
+ ) -> Dict[str, str]:
296
+ if request.method == "GET":
297
+ return self.parse_query_params(request.query_params)
298
+
299
+ return self.parse_json(await request.get_body())
300
+
190
301
  async def parse_http_body(
191
302
  self, request: AsyncHTTPRequestAdapter
192
303
  ) -> GraphQLRequestData:
193
- content_type = request.content_type or ""
304
+ content_type, params = parse_content_type(request.content_type or "")
194
305
 
195
306
  if request.method == "GET":
196
307
  data = self.parse_query_params(request.query_params)
197
308
  elif "application/json" in content_type:
198
309
  data = self.parse_json(await request.get_body())
199
- elif content_type.startswith("multipart/form-data"):
310
+ elif content_type == "multipart/form-data":
200
311
  data = await self.parse_multipart(request)
312
+ elif self._is_multipart_subscriptions(content_type, params):
313
+ data = await self.parse_multipart_subscriptions(request)
201
314
  else:
202
315
  raise HTTPException(400, "Unsupported content type")
203
316
 
@@ -69,5 +69,16 @@ class BaseView(Generic[Request]):
69
69
  graphql_ide=self.graphql_ide,
70
70
  )
71
71
 
72
+ def _is_multipart_subscriptions(
73
+ self, content_type: str, params: Dict[str, str]
74
+ ) -> bool:
75
+ if content_type != "multipart/mixed":
76
+ return False
77
+
78
+ if params.get("boundary") != "graphql":
79
+ return False
80
+
81
+ return params.get("subscriptionspec", "").startswith("1.0")
82
+
72
83
 
73
84
  __all__ = ["BaseView"]
@@ -0,0 +1,16 @@
1
+ from email.message import Message
2
+ from typing import Dict, Tuple
3
+
4
+
5
+ def parse_content_type(content_type: str) -> Tuple[str, Dict[str, str]]:
6
+ """Parse a content type header into a mime-type and a dictionary of parameters."""
7
+ email = Message()
8
+ email["content-type"] = content_type
9
+
10
+ params = email.get_params()
11
+
12
+ assert params
13
+
14
+ mime_type, _ = params.pop(0)
15
+
16
+ return mime_type, dict(params)
@@ -16,7 +16,11 @@ from graphql import GraphQLError
16
16
  from strawberry import UNSET
17
17
  from strawberry.exceptions import MissingQueryError
18
18
  from strawberry.file_uploads.utils import replace_placeholders_with_files
19
- from strawberry.http import GraphQLHTTPResponse, GraphQLRequestData, process_result
19
+ from strawberry.http import (
20
+ GraphQLHTTPResponse,
21
+ GraphQLRequestData,
22
+ process_result,
23
+ )
20
24
  from strawberry.http.ides import GraphQL_IDE
21
25
  from strawberry.schema import BaseSchema
22
26
  from strawberry.schema.exceptions import InvalidOperationTypeError
@@ -25,6 +29,7 @@ from strawberry.types.graphql import OperationType
25
29
 
26
30
  from .base import BaseView
27
31
  from .exceptions import HTTPException
32
+ from .parse_content_type import parse_content_type
28
33
  from .types import HTTPMethod, QueryParams
29
34
  from .typevars import Context, Request, Response, RootValue, SubResponse
30
35
 
@@ -131,14 +136,19 @@ class SyncBaseHTTPView(
131
136
  raise HTTPException(400, "File(s) missing in form data") from e
132
137
 
133
138
  def parse_http_body(self, request: SyncHTTPRequestAdapter) -> GraphQLRequestData:
134
- content_type = request.content_type or ""
139
+ content_type, params = parse_content_type(request.content_type or "")
135
140
 
136
141
  if request.method == "GET":
137
142
  data = self.parse_query_params(request.query_params)
138
143
  elif "application/json" in content_type:
139
144
  data = self.parse_json(request.body)
140
- elif content_type.startswith("multipart/form-data"):
145
+ # TODO: multipart via get?
146
+ elif content_type == "multipart/form-data":
141
147
  data = self.parse_multipart(request)
148
+ elif self._is_multipart_subscriptions(content_type, params):
149
+ raise HTTPException(
150
+ 400, "Multipart subcriptions are not supported in sync mode"
151
+ )
142
152
  else:
143
153
  raise HTTPException(400, "Unsupported content type")
144
154