prefect-client 2.19.2__py3-none-any.whl → 3.0.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. prefect/__init__.py +8 -56
  2. prefect/_internal/compatibility/deprecated.py +6 -115
  3. prefect/_internal/compatibility/experimental.py +4 -79
  4. prefect/_internal/concurrency/api.py +0 -34
  5. prefect/_internal/concurrency/calls.py +0 -6
  6. prefect/_internal/concurrency/cancellation.py +0 -3
  7. prefect/_internal/concurrency/event_loop.py +0 -20
  8. prefect/_internal/concurrency/inspection.py +3 -3
  9. prefect/_internal/concurrency/threads.py +35 -0
  10. prefect/_internal/concurrency/waiters.py +0 -28
  11. prefect/_internal/pydantic/__init__.py +0 -45
  12. prefect/_internal/pydantic/v1_schema.py +21 -22
  13. prefect/_internal/pydantic/v2_schema.py +0 -2
  14. prefect/_internal/pydantic/v2_validated_func.py +18 -23
  15. prefect/_internal/schemas/bases.py +44 -177
  16. prefect/_internal/schemas/fields.py +1 -43
  17. prefect/_internal/schemas/validators.py +60 -158
  18. prefect/artifacts.py +161 -14
  19. prefect/automations.py +39 -4
  20. prefect/blocks/abstract.py +1 -1
  21. prefect/blocks/core.py +268 -148
  22. prefect/blocks/fields.py +2 -57
  23. prefect/blocks/kubernetes.py +8 -12
  24. prefect/blocks/notifications.py +40 -20
  25. prefect/blocks/system.py +22 -11
  26. prefect/blocks/webhook.py +2 -9
  27. prefect/client/base.py +4 -4
  28. prefect/client/cloud.py +8 -13
  29. prefect/client/orchestration.py +347 -341
  30. prefect/client/schemas/actions.py +92 -86
  31. prefect/client/schemas/filters.py +20 -40
  32. prefect/client/schemas/objects.py +151 -145
  33. prefect/client/schemas/responses.py +16 -24
  34. prefect/client/schemas/schedules.py +47 -35
  35. prefect/client/subscriptions.py +2 -2
  36. prefect/client/utilities.py +5 -2
  37. prefect/concurrency/asyncio.py +3 -1
  38. prefect/concurrency/events.py +1 -1
  39. prefect/concurrency/services.py +6 -3
  40. prefect/context.py +195 -27
  41. prefect/deployments/__init__.py +5 -6
  42. prefect/deployments/base.py +7 -5
  43. prefect/deployments/flow_runs.py +185 -0
  44. prefect/deployments/runner.py +50 -45
  45. prefect/deployments/schedules.py +28 -23
  46. prefect/deployments/steps/__init__.py +0 -1
  47. prefect/deployments/steps/core.py +1 -0
  48. prefect/deployments/steps/pull.py +7 -21
  49. prefect/engine.py +12 -2422
  50. prefect/events/actions.py +17 -23
  51. prefect/events/cli/automations.py +19 -6
  52. prefect/events/clients.py +14 -37
  53. prefect/events/filters.py +14 -18
  54. prefect/events/related.py +2 -2
  55. prefect/events/schemas/__init__.py +0 -5
  56. prefect/events/schemas/automations.py +55 -46
  57. prefect/events/schemas/deployment_triggers.py +7 -197
  58. prefect/events/schemas/events.py +34 -65
  59. prefect/events/schemas/labelling.py +10 -14
  60. prefect/events/utilities.py +2 -3
  61. prefect/events/worker.py +2 -3
  62. prefect/filesystems.py +6 -517
  63. prefect/{new_flow_engine.py → flow_engine.py} +313 -72
  64. prefect/flow_runs.py +377 -5
  65. prefect/flows.py +307 -166
  66. prefect/futures.py +186 -345
  67. prefect/infrastructure/__init__.py +0 -27
  68. prefect/infrastructure/provisioners/__init__.py +5 -3
  69. prefect/infrastructure/provisioners/cloud_run.py +11 -6
  70. prefect/infrastructure/provisioners/container_instance.py +11 -7
  71. prefect/infrastructure/provisioners/ecs.py +6 -4
  72. prefect/infrastructure/provisioners/modal.py +8 -5
  73. prefect/input/actions.py +2 -4
  74. prefect/input/run_input.py +5 -7
  75. prefect/logging/formatters.py +0 -2
  76. prefect/logging/handlers.py +3 -11
  77. prefect/logging/loggers.py +2 -2
  78. prefect/manifests.py +2 -1
  79. prefect/records/__init__.py +1 -0
  80. prefect/records/result_store.py +42 -0
  81. prefect/records/store.py +9 -0
  82. prefect/results.py +43 -39
  83. prefect/runner/runner.py +19 -15
  84. prefect/runner/server.py +6 -10
  85. prefect/runner/storage.py +3 -8
  86. prefect/runner/submit.py +2 -2
  87. prefect/runner/utils.py +2 -2
  88. prefect/serializers.py +24 -35
  89. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
  90. prefect/settings.py +70 -133
  91. prefect/states.py +17 -47
  92. prefect/task_engine.py +697 -58
  93. prefect/task_runners.py +269 -301
  94. prefect/task_server.py +53 -34
  95. prefect/tasks.py +327 -337
  96. prefect/transactions.py +220 -0
  97. prefect/types/__init__.py +61 -82
  98. prefect/utilities/asyncutils.py +195 -136
  99. prefect/utilities/callables.py +311 -43
  100. prefect/utilities/collections.py +23 -38
  101. prefect/utilities/dispatch.py +11 -3
  102. prefect/utilities/dockerutils.py +4 -0
  103. prefect/utilities/engine.py +140 -20
  104. prefect/utilities/importtools.py +97 -27
  105. prefect/utilities/pydantic.py +128 -38
  106. prefect/utilities/schema_tools/hydration.py +5 -1
  107. prefect/utilities/templating.py +12 -2
  108. prefect/variables.py +78 -61
  109. prefect/workers/__init__.py +0 -1
  110. prefect/workers/base.py +15 -17
  111. prefect/workers/process.py +3 -8
  112. prefect/workers/server.py +2 -2
  113. {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/METADATA +22 -21
  114. prefect_client-3.0.0rc1.dist-info/RECORD +176 -0
  115. prefect/_internal/pydantic/_base_model.py +0 -51
  116. prefect/_internal/pydantic/_compat.py +0 -82
  117. prefect/_internal/pydantic/_flags.py +0 -20
  118. prefect/_internal/pydantic/_types.py +0 -8
  119. prefect/_internal/pydantic/utilities/__init__.py +0 -0
  120. prefect/_internal/pydantic/utilities/config_dict.py +0 -72
  121. prefect/_internal/pydantic/utilities/field_validator.py +0 -150
  122. prefect/_internal/pydantic/utilities/model_construct.py +0 -56
  123. prefect/_internal/pydantic/utilities/model_copy.py +0 -55
  124. prefect/_internal/pydantic/utilities/model_dump.py +0 -136
  125. prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
  126. prefect/_internal/pydantic/utilities/model_fields.py +0 -50
  127. prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
  128. prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
  129. prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
  130. prefect/_internal/pydantic/utilities/model_validate.py +0 -75
  131. prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
  132. prefect/_internal/pydantic/utilities/model_validator.py +0 -87
  133. prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
  134. prefect/_vendor/__init__.py +0 -0
  135. prefect/_vendor/fastapi/__init__.py +0 -25
  136. prefect/_vendor/fastapi/applications.py +0 -946
  137. prefect/_vendor/fastapi/background.py +0 -3
  138. prefect/_vendor/fastapi/concurrency.py +0 -44
  139. prefect/_vendor/fastapi/datastructures.py +0 -58
  140. prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
  141. prefect/_vendor/fastapi/dependencies/models.py +0 -64
  142. prefect/_vendor/fastapi/dependencies/utils.py +0 -877
  143. prefect/_vendor/fastapi/encoders.py +0 -177
  144. prefect/_vendor/fastapi/exception_handlers.py +0 -40
  145. prefect/_vendor/fastapi/exceptions.py +0 -46
  146. prefect/_vendor/fastapi/logger.py +0 -3
  147. prefect/_vendor/fastapi/middleware/__init__.py +0 -1
  148. prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
  149. prefect/_vendor/fastapi/middleware/cors.py +0 -3
  150. prefect/_vendor/fastapi/middleware/gzip.py +0 -3
  151. prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
  152. prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
  153. prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
  154. prefect/_vendor/fastapi/openapi/__init__.py +0 -0
  155. prefect/_vendor/fastapi/openapi/constants.py +0 -2
  156. prefect/_vendor/fastapi/openapi/docs.py +0 -203
  157. prefect/_vendor/fastapi/openapi/models.py +0 -480
  158. prefect/_vendor/fastapi/openapi/utils.py +0 -485
  159. prefect/_vendor/fastapi/param_functions.py +0 -340
  160. prefect/_vendor/fastapi/params.py +0 -453
  161. prefect/_vendor/fastapi/requests.py +0 -4
  162. prefect/_vendor/fastapi/responses.py +0 -40
  163. prefect/_vendor/fastapi/routing.py +0 -1331
  164. prefect/_vendor/fastapi/security/__init__.py +0 -15
  165. prefect/_vendor/fastapi/security/api_key.py +0 -98
  166. prefect/_vendor/fastapi/security/base.py +0 -6
  167. prefect/_vendor/fastapi/security/http.py +0 -172
  168. prefect/_vendor/fastapi/security/oauth2.py +0 -227
  169. prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
  170. prefect/_vendor/fastapi/security/utils.py +0 -10
  171. prefect/_vendor/fastapi/staticfiles.py +0 -1
  172. prefect/_vendor/fastapi/templating.py +0 -3
  173. prefect/_vendor/fastapi/testclient.py +0 -1
  174. prefect/_vendor/fastapi/types.py +0 -3
  175. prefect/_vendor/fastapi/utils.py +0 -235
  176. prefect/_vendor/fastapi/websockets.py +0 -7
  177. prefect/_vendor/starlette/__init__.py +0 -1
  178. prefect/_vendor/starlette/_compat.py +0 -28
  179. prefect/_vendor/starlette/_exception_handler.py +0 -80
  180. prefect/_vendor/starlette/_utils.py +0 -88
  181. prefect/_vendor/starlette/applications.py +0 -261
  182. prefect/_vendor/starlette/authentication.py +0 -159
  183. prefect/_vendor/starlette/background.py +0 -43
  184. prefect/_vendor/starlette/concurrency.py +0 -59
  185. prefect/_vendor/starlette/config.py +0 -151
  186. prefect/_vendor/starlette/convertors.py +0 -87
  187. prefect/_vendor/starlette/datastructures.py +0 -707
  188. prefect/_vendor/starlette/endpoints.py +0 -130
  189. prefect/_vendor/starlette/exceptions.py +0 -60
  190. prefect/_vendor/starlette/formparsers.py +0 -276
  191. prefect/_vendor/starlette/middleware/__init__.py +0 -17
  192. prefect/_vendor/starlette/middleware/authentication.py +0 -52
  193. prefect/_vendor/starlette/middleware/base.py +0 -220
  194. prefect/_vendor/starlette/middleware/cors.py +0 -176
  195. prefect/_vendor/starlette/middleware/errors.py +0 -265
  196. prefect/_vendor/starlette/middleware/exceptions.py +0 -74
  197. prefect/_vendor/starlette/middleware/gzip.py +0 -113
  198. prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
  199. prefect/_vendor/starlette/middleware/sessions.py +0 -82
  200. prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
  201. prefect/_vendor/starlette/middleware/wsgi.py +0 -147
  202. prefect/_vendor/starlette/requests.py +0 -328
  203. prefect/_vendor/starlette/responses.py +0 -347
  204. prefect/_vendor/starlette/routing.py +0 -933
  205. prefect/_vendor/starlette/schemas.py +0 -154
  206. prefect/_vendor/starlette/staticfiles.py +0 -248
  207. prefect/_vendor/starlette/status.py +0 -199
  208. prefect/_vendor/starlette/templating.py +0 -231
  209. prefect/_vendor/starlette/testclient.py +0 -804
  210. prefect/_vendor/starlette/types.py +0 -30
  211. prefect/_vendor/starlette/websockets.py +0 -193
  212. prefect/agent.py +0 -698
  213. prefect/deployments/deployments.py +0 -1042
  214. prefect/deprecated/__init__.py +0 -0
  215. prefect/deprecated/data_documents.py +0 -350
  216. prefect/deprecated/packaging/__init__.py +0 -12
  217. prefect/deprecated/packaging/base.py +0 -96
  218. prefect/deprecated/packaging/docker.py +0 -146
  219. prefect/deprecated/packaging/file.py +0 -92
  220. prefect/deprecated/packaging/orion.py +0 -80
  221. prefect/deprecated/packaging/serializers.py +0 -171
  222. prefect/events/instrument.py +0 -135
  223. prefect/infrastructure/base.py +0 -323
  224. prefect/infrastructure/container.py +0 -818
  225. prefect/infrastructure/kubernetes.py +0 -920
  226. prefect/infrastructure/process.py +0 -289
  227. prefect/new_task_engine.py +0 -423
  228. prefect/pydantic/__init__.py +0 -76
  229. prefect/pydantic/main.py +0 -39
  230. prefect/software/__init__.py +0 -2
  231. prefect/software/base.py +0 -50
  232. prefect/software/conda.py +0 -199
  233. prefect/software/pip.py +0 -122
  234. prefect/software/python.py +0 -52
  235. prefect/workers/block.py +0 -218
  236. prefect_client-2.19.2.dist-info/RECORD +0 -292
  237. {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/LICENSE +0 -0
  238. {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/WHEEL +0 -0
  239. {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/top_level.txt +0 -0
@@ -1,74 +0,0 @@
1
- import typing
2
-
3
- from prefect._vendor.starlette._exception_handler import (
4
- ExceptionHandlers,
5
- StatusHandlers,
6
- wrap_app_handling_exceptions,
7
- )
8
- from prefect._vendor.starlette.exceptions import HTTPException, WebSocketException
9
- from prefect._vendor.starlette.requests import Request
10
- from prefect._vendor.starlette.responses import PlainTextResponse, Response
11
- from prefect._vendor.starlette.types import ASGIApp, Receive, Scope, Send
12
- from prefect._vendor.starlette.websockets import WebSocket
13
-
14
-
15
- class ExceptionMiddleware:
16
- def __init__(
17
- self,
18
- app: ASGIApp,
19
- handlers: typing.Optional[
20
- typing.Mapping[typing.Any, typing.Callable[[Request, Exception], Response]]
21
- ] = None,
22
- debug: bool = False,
23
- ) -> None:
24
- self.app = app
25
- self.debug = debug # TODO: We ought to handle 404 cases if debug is set.
26
- self._status_handlers: StatusHandlers = {}
27
- self._exception_handlers: ExceptionHandlers = {
28
- HTTPException: self.http_exception,
29
- WebSocketException: self.websocket_exception,
30
- }
31
- if handlers is not None:
32
- for key, value in handlers.items():
33
- self.add_exception_handler(key, value)
34
-
35
- def add_exception_handler(
36
- self,
37
- exc_class_or_status_code: typing.Union[int, typing.Type[Exception]],
38
- handler: typing.Callable[[Request, Exception], Response],
39
- ) -> None:
40
- if isinstance(exc_class_or_status_code, int):
41
- self._status_handlers[exc_class_or_status_code] = handler
42
- else:
43
- assert issubclass(exc_class_or_status_code, Exception)
44
- self._exception_handlers[exc_class_or_status_code] = handler
45
-
46
- async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
47
- if scope["type"] not in ("http", "websocket"):
48
- await self.app(scope, receive, send)
49
- return
50
-
51
- scope["starlette.exception_handlers"] = (
52
- self._exception_handlers,
53
- self._status_handlers,
54
- )
55
-
56
- conn: typing.Union[Request, WebSocket]
57
- if scope["type"] == "http":
58
- conn = Request(scope, receive, send)
59
- else:
60
- conn = WebSocket(scope, receive, send)
61
-
62
- await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
63
-
64
- def http_exception(self, request: Request, exc: Exception) -> Response:
65
- assert isinstance(exc, HTTPException)
66
- if exc.status_code in {204, 304}:
67
- return Response(status_code=exc.status_code, headers=exc.headers)
68
- return PlainTextResponse(
69
- exc.detail, status_code=exc.status_code, headers=exc.headers
70
- )
71
-
72
- async def websocket_exception(self, websocket: WebSocket, exc: Exception) -> None:
73
- assert isinstance(exc, WebSocketException)
74
- await websocket.close(code=exc.code, reason=exc.reason) # pragma: no cover
@@ -1,113 +0,0 @@
1
- import gzip
2
- import io
3
- import typing
4
-
5
- from prefect._vendor.starlette.datastructures import Headers, MutableHeaders
6
- from prefect._vendor.starlette.types import ASGIApp, Message, Receive, Scope, Send
7
-
8
-
9
- class GZipMiddleware:
10
- def __init__(
11
- self, app: ASGIApp, minimum_size: int = 500, compresslevel: int = 9
12
- ) -> None:
13
- self.app = app
14
- self.minimum_size = minimum_size
15
- self.compresslevel = compresslevel
16
-
17
- async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
18
- if scope["type"] == "http":
19
- headers = Headers(scope=scope)
20
- if "gzip" in headers.get("Accept-Encoding", ""):
21
- responder = GZipResponder(
22
- self.app, self.minimum_size, compresslevel=self.compresslevel
23
- )
24
- await responder(scope, receive, send)
25
- return
26
- await self.app(scope, receive, send)
27
-
28
-
29
- class GZipResponder:
30
- def __init__(self, app: ASGIApp, minimum_size: int, compresslevel: int = 9) -> None:
31
- self.app = app
32
- self.minimum_size = minimum_size
33
- self.send: Send = unattached_send
34
- self.initial_message: Message = {}
35
- self.started = False
36
- self.content_encoding_set = False
37
- self.gzip_buffer = io.BytesIO()
38
- self.gzip_file = gzip.GzipFile(
39
- mode="wb", fileobj=self.gzip_buffer, compresslevel=compresslevel
40
- )
41
-
42
- async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
43
- self.send = send
44
- await self.app(scope, receive, self.send_with_gzip)
45
-
46
- async def send_with_gzip(self, message: Message) -> None:
47
- message_type = message["type"]
48
- if message_type == "http.response.start":
49
- # Don't send the initial message until we've determined how to
50
- # modify the outgoing headers correctly.
51
- self.initial_message = message
52
- headers = Headers(raw=self.initial_message["headers"])
53
- self.content_encoding_set = "content-encoding" in headers
54
- elif message_type == "http.response.body" and self.content_encoding_set:
55
- if not self.started:
56
- self.started = True
57
- await self.send(self.initial_message)
58
- await self.send(message)
59
- elif message_type == "http.response.body" and not self.started:
60
- self.started = True
61
- body = message.get("body", b"")
62
- more_body = message.get("more_body", False)
63
- if len(body) < self.minimum_size and not more_body:
64
- # Don't apply GZip to small outgoing responses.
65
- await self.send(self.initial_message)
66
- await self.send(message)
67
- elif not more_body:
68
- # Standard GZip response.
69
- self.gzip_file.write(body)
70
- self.gzip_file.close()
71
- body = self.gzip_buffer.getvalue()
72
-
73
- headers = MutableHeaders(raw=self.initial_message["headers"])
74
- headers["Content-Encoding"] = "gzip"
75
- headers["Content-Length"] = str(len(body))
76
- headers.add_vary_header("Accept-Encoding")
77
- message["body"] = body
78
-
79
- await self.send(self.initial_message)
80
- await self.send(message)
81
- else:
82
- # Initial body in streaming GZip response.
83
- headers = MutableHeaders(raw=self.initial_message["headers"])
84
- headers["Content-Encoding"] = "gzip"
85
- headers.add_vary_header("Accept-Encoding")
86
- del headers["Content-Length"]
87
-
88
- self.gzip_file.write(body)
89
- message["body"] = self.gzip_buffer.getvalue()
90
- self.gzip_buffer.seek(0)
91
- self.gzip_buffer.truncate()
92
-
93
- await self.send(self.initial_message)
94
- await self.send(message)
95
-
96
- elif message_type == "http.response.body":
97
- # Remaining body in streaming GZip response.
98
- body = message.get("body", b"")
99
- more_body = message.get("more_body", False)
100
-
101
- self.gzip_file.write(body)
102
- if not more_body:
103
- self.gzip_file.close()
104
-
105
- message["body"] = self.gzip_buffer.getvalue()
106
- self.gzip_buffer.seek(0)
107
- self.gzip_buffer.truncate()
108
-
109
- await self.send(message)
110
-
111
-
112
- async def unattached_send(message: Message) -> typing.NoReturn:
113
- raise RuntimeError("send awaitable not set") # pragma: no cover
@@ -1,19 +0,0 @@
1
- from prefect._vendor.starlette.datastructures import URL
2
- from prefect._vendor.starlette.responses import RedirectResponse
3
- from prefect._vendor.starlette.types import ASGIApp, Receive, Scope, Send
4
-
5
-
6
- class HTTPSRedirectMiddleware:
7
- def __init__(self, app: ASGIApp) -> None:
8
- self.app = app
9
-
10
- async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
11
- if scope["type"] in ("http", "websocket") and scope["scheme"] in ("http", "ws"):
12
- url = URL(scope=scope)
13
- redirect_scheme = {"http": "https", "ws": "wss"}[url.scheme]
14
- netloc = url.hostname if url.port in (80, 443) else url.netloc
15
- url = url.replace(scheme=redirect_scheme, netloc=netloc)
16
- response = RedirectResponse(url, status_code=307)
17
- await response(scope, receive, send)
18
- else:
19
- await self.app(scope, receive, send)
@@ -1,82 +0,0 @@
1
- import json
2
- import typing
3
- from base64 import b64decode, b64encode
4
-
5
- import itsdangerous
6
- from itsdangerous.exc import BadSignature
7
- from prefect._vendor.starlette.datastructures import MutableHeaders, Secret
8
- from prefect._vendor.starlette.requests import HTTPConnection
9
- from prefect._vendor.starlette.types import ASGIApp, Message, Receive, Scope, Send
10
-
11
-
12
- class SessionMiddleware:
13
- def __init__(
14
- self,
15
- app: ASGIApp,
16
- secret_key: typing.Union[str, Secret],
17
- session_cookie: str = "session",
18
- max_age: typing.Optional[int] = 14 * 24 * 60 * 60, # 14 days, in seconds
19
- path: str = "/",
20
- same_site: typing.Literal["lax", "strict", "none"] = "lax",
21
- https_only: bool = False,
22
- domain: typing.Optional[str] = None,
23
- ) -> None:
24
- self.app = app
25
- self.signer = itsdangerous.TimestampSigner(str(secret_key))
26
- self.session_cookie = session_cookie
27
- self.max_age = max_age
28
- self.path = path
29
- self.security_flags = "httponly; samesite=" + same_site
30
- if https_only: # Secure flag can be used with HTTPS only
31
- self.security_flags += "; secure"
32
- if domain is not None:
33
- self.security_flags += f"; domain={domain}"
34
-
35
- async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
36
- if scope["type"] not in ("http", "websocket"): # pragma: no cover
37
- await self.app(scope, receive, send)
38
- return
39
-
40
- connection = HTTPConnection(scope)
41
- initial_session_was_empty = True
42
-
43
- if self.session_cookie in connection.cookies:
44
- data = connection.cookies[self.session_cookie].encode("utf-8")
45
- try:
46
- data = self.signer.unsign(data, max_age=self.max_age)
47
- scope["session"] = json.loads(b64decode(data))
48
- initial_session_was_empty = False
49
- except BadSignature:
50
- scope["session"] = {}
51
- else:
52
- scope["session"] = {}
53
-
54
- async def send_wrapper(message: Message) -> None:
55
- if message["type"] == "http.response.start":
56
- if scope["session"]:
57
- # We have session data to persist.
58
- data = b64encode(json.dumps(scope["session"]).encode("utf-8"))
59
- data = self.signer.sign(data)
60
- headers = MutableHeaders(scope=message)
61
- header_value = "{session_cookie}={data}; path={path}; {max_age}{security_flags}".format( # noqa E501
62
- session_cookie=self.session_cookie,
63
- data=data.decode("utf-8"),
64
- path=self.path,
65
- max_age=f"Max-Age={self.max_age}; " if self.max_age else "",
66
- security_flags=self.security_flags,
67
- )
68
- headers.append("Set-Cookie", header_value)
69
- elif not initial_session_was_empty:
70
- # The session has been cleared.
71
- headers = MutableHeaders(scope=message)
72
- header_value = "{session_cookie}={data}; path={path}; {expires}{security_flags}".format( # noqa E501
73
- session_cookie=self.session_cookie,
74
- data="null",
75
- path=self.path,
76
- expires="expires=Thu, 01 Jan 1970 00:00:00 GMT; ",
77
- security_flags=self.security_flags,
78
- )
79
- headers.append("Set-Cookie", header_value)
80
- await send(message)
81
-
82
- await self.app(scope, receive, send_wrapper)
@@ -1,64 +0,0 @@
1
- import typing
2
-
3
- from prefect._vendor.starlette.datastructures import URL, Headers
4
- from prefect._vendor.starlette.responses import (
5
- PlainTextResponse,
6
- RedirectResponse,
7
- Response,
8
- )
9
- from prefect._vendor.starlette.types import ASGIApp, Receive, Scope, Send
10
-
11
- ENFORCE_DOMAIN_WILDCARD = "Domain wildcard patterns must be like '*.example.com'."
12
-
13
-
14
- class TrustedHostMiddleware:
15
- def __init__(
16
- self,
17
- app: ASGIApp,
18
- allowed_hosts: typing.Optional[typing.Sequence[str]] = None,
19
- www_redirect: bool = True,
20
- ) -> None:
21
- if allowed_hosts is None:
22
- allowed_hosts = ["*"]
23
-
24
- for pattern in allowed_hosts:
25
- assert "*" not in pattern[1:], ENFORCE_DOMAIN_WILDCARD
26
- if pattern.startswith("*") and pattern != "*":
27
- assert pattern.startswith("*."), ENFORCE_DOMAIN_WILDCARD
28
- self.app = app
29
- self.allowed_hosts = list(allowed_hosts)
30
- self.allow_any = "*" in allowed_hosts
31
- self.www_redirect = www_redirect
32
-
33
- async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
34
- if self.allow_any or scope["type"] not in (
35
- "http",
36
- "websocket",
37
- ): # pragma: no cover
38
- await self.app(scope, receive, send)
39
- return
40
-
41
- headers = Headers(scope=scope)
42
- host = headers.get("host", "").split(":")[0]
43
- is_valid_host = False
44
- found_www_redirect = False
45
- for pattern in self.allowed_hosts:
46
- if host == pattern or (
47
- pattern.startswith("*") and host.endswith(pattern[1:])
48
- ):
49
- is_valid_host = True
50
- break
51
- elif "www." + host == pattern:
52
- found_www_redirect = True
53
-
54
- if is_valid_host:
55
- await self.app(scope, receive, send)
56
- else:
57
- response: Response
58
- if found_www_redirect and self.www_redirect:
59
- url = URL(scope=scope)
60
- redirect_url = url.replace(netloc="www." + url.netloc)
61
- response = RedirectResponse(url=str(redirect_url))
62
- else:
63
- response = PlainTextResponse("Invalid host header", status_code=400)
64
- await response(scope, receive, send)
@@ -1,147 +0,0 @@
1
- import io
2
- import math
3
- import sys
4
- import typing
5
- import warnings
6
-
7
- import anyio
8
- from anyio.abc import ObjectReceiveStream, ObjectSendStream
9
- from prefect._vendor.starlette.types import Receive, Scope, Send
10
-
11
- warnings.warn(
12
- "starlette.middleware.wsgi is deprecated and will be removed in a future release. "
13
- "Please refer to https://github.com/abersheeran/a2wsgi as a replacement.",
14
- DeprecationWarning,
15
- )
16
-
17
-
18
- def build_environ(scope: Scope, body: bytes) -> typing.Dict[str, typing.Any]:
19
- """
20
- Builds a scope and request body into a WSGI environ object.
21
- """
22
- environ = {
23
- "REQUEST_METHOD": scope["method"],
24
- "SCRIPT_NAME": scope.get("root_path", "").encode("utf8").decode("latin1"),
25
- "PATH_INFO": scope["path"].encode("utf8").decode("latin1"),
26
- "QUERY_STRING": scope["query_string"].decode("ascii"),
27
- "SERVER_PROTOCOL": f"HTTP/{scope['http_version']}",
28
- "wsgi.version": (1, 0),
29
- "wsgi.url_scheme": scope.get("scheme", "http"),
30
- "wsgi.input": io.BytesIO(body),
31
- "wsgi.errors": sys.stdout,
32
- "wsgi.multithread": True,
33
- "wsgi.multiprocess": True,
34
- "wsgi.run_once": False,
35
- }
36
-
37
- # Get server name and port - required in WSGI, not in ASGI
38
- server = scope.get("server") or ("localhost", 80)
39
- environ["SERVER_NAME"] = server[0]
40
- environ["SERVER_PORT"] = server[1]
41
-
42
- # Get client IP address
43
- if scope.get("client"):
44
- environ["REMOTE_ADDR"] = scope["client"][0]
45
-
46
- # Go through headers and make them into environ entries
47
- for name, value in scope.get("headers", []):
48
- name = name.decode("latin1")
49
- if name == "content-length":
50
- corrected_name = "CONTENT_LENGTH"
51
- elif name == "content-type":
52
- corrected_name = "CONTENT_TYPE"
53
- else:
54
- corrected_name = f"HTTP_{name}".upper().replace("-", "_")
55
- # HTTPbis say only ASCII chars are allowed in headers, but we latin1 just in
56
- # case
57
- value = value.decode("latin1")
58
- if corrected_name in environ:
59
- value = environ[corrected_name] + "," + value
60
- environ[corrected_name] = value
61
- return environ
62
-
63
-
64
- class WSGIMiddleware:
65
- def __init__(self, app: typing.Callable[..., typing.Any]) -> None:
66
- self.app = app
67
-
68
- async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
69
- assert scope["type"] == "http"
70
- responder = WSGIResponder(self.app, scope)
71
- await responder(receive, send)
72
-
73
-
74
- class WSGIResponder:
75
- stream_send: ObjectSendStream[typing.MutableMapping[str, typing.Any]]
76
- stream_receive: ObjectReceiveStream[typing.MutableMapping[str, typing.Any]]
77
-
78
- def __init__(self, app: typing.Callable[..., typing.Any], scope: Scope) -> None:
79
- self.app = app
80
- self.scope = scope
81
- self.status = None
82
- self.response_headers = None
83
- self.stream_send, self.stream_receive = anyio.create_memory_object_stream(
84
- math.inf
85
- )
86
- self.response_started = False
87
- self.exc_info: typing.Any = None
88
-
89
- async def __call__(self, receive: Receive, send: Send) -> None:
90
- body = b""
91
- more_body = True
92
- while more_body:
93
- message = await receive()
94
- body += message.get("body", b"")
95
- more_body = message.get("more_body", False)
96
- environ = build_environ(self.scope, body)
97
-
98
- async with anyio.create_task_group() as task_group:
99
- task_group.start_soon(self.sender, send)
100
- async with self.stream_send:
101
- await anyio.to_thread.run_sync(self.wsgi, environ, self.start_response)
102
- if self.exc_info is not None:
103
- raise self.exc_info[0].with_traceback(self.exc_info[1], self.exc_info[2])
104
-
105
- async def sender(self, send: Send) -> None:
106
- async with self.stream_receive:
107
- async for message in self.stream_receive:
108
- await send(message)
109
-
110
- def start_response(
111
- self,
112
- status: str,
113
- response_headers: typing.List[typing.Tuple[str, str]],
114
- exc_info: typing.Any = None,
115
- ) -> None:
116
- self.exc_info = exc_info
117
- if not self.response_started:
118
- self.response_started = True
119
- status_code_string, _ = status.split(" ", 1)
120
- status_code = int(status_code_string)
121
- headers = [
122
- (name.strip().encode("ascii").lower(), value.strip().encode("ascii"))
123
- for name, value in response_headers
124
- ]
125
- anyio.from_thread.run(
126
- self.stream_send.send,
127
- {
128
- "type": "http.response.start",
129
- "status": status_code,
130
- "headers": headers,
131
- },
132
- )
133
-
134
- def wsgi(
135
- self,
136
- environ: typing.Dict[str, typing.Any],
137
- start_response: typing.Callable[..., typing.Any],
138
- ) -> None:
139
- for chunk in self.app(environ, start_response):
140
- anyio.from_thread.run(
141
- self.stream_send.send,
142
- {"type": "http.response.body", "body": chunk, "more_body": True},
143
- )
144
-
145
- anyio.from_thread.run(
146
- self.stream_send.send, {"type": "http.response.body", "body": b""}
147
- )