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