prefect-client 2.20.4__py3-none-any.whl → 3.0.0__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 (288) hide show
  1. prefect/__init__.py +74 -110
  2. prefect/_internal/compatibility/deprecated.py +6 -115
  3. prefect/_internal/compatibility/experimental.py +4 -79
  4. prefect/_internal/compatibility/migration.py +166 -0
  5. prefect/_internal/concurrency/__init__.py +2 -2
  6. prefect/_internal/concurrency/api.py +1 -35
  7. prefect/_internal/concurrency/calls.py +0 -6
  8. prefect/_internal/concurrency/cancellation.py +0 -3
  9. prefect/_internal/concurrency/event_loop.py +0 -20
  10. prefect/_internal/concurrency/inspection.py +3 -3
  11. prefect/_internal/concurrency/primitives.py +1 -0
  12. prefect/_internal/concurrency/services.py +23 -0
  13. prefect/_internal/concurrency/threads.py +35 -0
  14. prefect/_internal/concurrency/waiters.py +0 -28
  15. prefect/_internal/integrations.py +7 -0
  16. prefect/_internal/pydantic/__init__.py +0 -45
  17. prefect/_internal/pydantic/annotations/pendulum.py +2 -2
  18. prefect/_internal/pydantic/v1_schema.py +21 -22
  19. prefect/_internal/pydantic/v2_schema.py +0 -2
  20. prefect/_internal/pydantic/v2_validated_func.py +18 -23
  21. prefect/_internal/pytz.py +1 -1
  22. prefect/_internal/retries.py +61 -0
  23. prefect/_internal/schemas/bases.py +45 -177
  24. prefect/_internal/schemas/fields.py +1 -43
  25. prefect/_internal/schemas/validators.py +47 -233
  26. prefect/agent.py +3 -695
  27. prefect/artifacts.py +173 -14
  28. prefect/automations.py +39 -4
  29. prefect/blocks/abstract.py +1 -1
  30. prefect/blocks/core.py +405 -153
  31. prefect/blocks/fields.py +2 -57
  32. prefect/blocks/notifications.py +43 -28
  33. prefect/blocks/redis.py +168 -0
  34. prefect/blocks/system.py +67 -20
  35. prefect/blocks/webhook.py +2 -9
  36. prefect/cache_policies.py +239 -0
  37. prefect/client/__init__.py +4 -0
  38. prefect/client/base.py +33 -27
  39. prefect/client/cloud.py +65 -20
  40. prefect/client/collections.py +1 -1
  41. prefect/client/orchestration.py +650 -442
  42. prefect/client/schemas/actions.py +115 -100
  43. prefect/client/schemas/filters.py +46 -52
  44. prefect/client/schemas/objects.py +228 -178
  45. prefect/client/schemas/responses.py +18 -36
  46. prefect/client/schemas/schedules.py +55 -36
  47. prefect/client/schemas/sorting.py +2 -0
  48. prefect/client/subscriptions.py +8 -7
  49. prefect/client/types/flexible_schedule_list.py +11 -0
  50. prefect/client/utilities.py +9 -6
  51. prefect/concurrency/asyncio.py +60 -11
  52. prefect/concurrency/context.py +24 -0
  53. prefect/concurrency/events.py +2 -2
  54. prefect/concurrency/services.py +46 -16
  55. prefect/concurrency/sync.py +51 -7
  56. prefect/concurrency/v1/asyncio.py +143 -0
  57. prefect/concurrency/v1/context.py +27 -0
  58. prefect/concurrency/v1/events.py +61 -0
  59. prefect/concurrency/v1/services.py +116 -0
  60. prefect/concurrency/v1/sync.py +92 -0
  61. prefect/context.py +246 -149
  62. prefect/deployments/__init__.py +33 -18
  63. prefect/deployments/base.py +10 -15
  64. prefect/deployments/deployments.py +2 -1048
  65. prefect/deployments/flow_runs.py +178 -0
  66. prefect/deployments/runner.py +72 -173
  67. prefect/deployments/schedules.py +31 -25
  68. prefect/deployments/steps/__init__.py +0 -1
  69. prefect/deployments/steps/core.py +7 -0
  70. prefect/deployments/steps/pull.py +15 -21
  71. prefect/deployments/steps/utility.py +2 -1
  72. prefect/docker/__init__.py +20 -0
  73. prefect/docker/docker_image.py +82 -0
  74. prefect/engine.py +15 -2475
  75. prefect/events/actions.py +17 -23
  76. prefect/events/cli/automations.py +20 -7
  77. prefect/events/clients.py +142 -80
  78. prefect/events/filters.py +14 -18
  79. prefect/events/related.py +74 -75
  80. prefect/events/schemas/__init__.py +0 -5
  81. prefect/events/schemas/automations.py +55 -46
  82. prefect/events/schemas/deployment_triggers.py +7 -197
  83. prefect/events/schemas/events.py +46 -65
  84. prefect/events/schemas/labelling.py +10 -14
  85. prefect/events/utilities.py +4 -5
  86. prefect/events/worker.py +23 -8
  87. prefect/exceptions.py +15 -0
  88. prefect/filesystems.py +30 -529
  89. prefect/flow_engine.py +827 -0
  90. prefect/flow_runs.py +379 -7
  91. prefect/flows.py +470 -360
  92. prefect/futures.py +382 -331
  93. prefect/infrastructure/__init__.py +5 -26
  94. prefect/infrastructure/base.py +3 -320
  95. prefect/infrastructure/provisioners/__init__.py +5 -3
  96. prefect/infrastructure/provisioners/cloud_run.py +13 -8
  97. prefect/infrastructure/provisioners/container_instance.py +14 -9
  98. prefect/infrastructure/provisioners/ecs.py +10 -8
  99. prefect/infrastructure/provisioners/modal.py +8 -5
  100. prefect/input/__init__.py +4 -0
  101. prefect/input/actions.py +2 -4
  102. prefect/input/run_input.py +9 -9
  103. prefect/logging/formatters.py +2 -4
  104. prefect/logging/handlers.py +9 -14
  105. prefect/logging/loggers.py +5 -5
  106. prefect/main.py +72 -0
  107. prefect/plugins.py +2 -64
  108. prefect/profiles.toml +16 -2
  109. prefect/records/__init__.py +1 -0
  110. prefect/records/base.py +223 -0
  111. prefect/records/filesystem.py +207 -0
  112. prefect/records/memory.py +178 -0
  113. prefect/records/result_store.py +64 -0
  114. prefect/results.py +577 -504
  115. prefect/runner/runner.py +117 -47
  116. prefect/runner/server.py +32 -34
  117. prefect/runner/storage.py +3 -12
  118. prefect/runner/submit.py +2 -10
  119. prefect/runner/utils.py +2 -2
  120. prefect/runtime/__init__.py +1 -0
  121. prefect/runtime/deployment.py +1 -0
  122. prefect/runtime/flow_run.py +40 -5
  123. prefect/runtime/task_run.py +1 -0
  124. prefect/serializers.py +28 -39
  125. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
  126. prefect/settings.py +209 -332
  127. prefect/states.py +160 -63
  128. prefect/task_engine.py +1478 -57
  129. prefect/task_runners.py +383 -287
  130. prefect/task_runs.py +240 -0
  131. prefect/task_worker.py +463 -0
  132. prefect/tasks.py +684 -374
  133. prefect/transactions.py +410 -0
  134. prefect/types/__init__.py +72 -86
  135. prefect/types/entrypoint.py +13 -0
  136. prefect/utilities/annotations.py +4 -3
  137. prefect/utilities/asyncutils.py +227 -148
  138. prefect/utilities/callables.py +137 -45
  139. prefect/utilities/collections.py +134 -86
  140. prefect/utilities/dispatch.py +27 -14
  141. prefect/utilities/dockerutils.py +11 -4
  142. prefect/utilities/engine.py +186 -32
  143. prefect/utilities/filesystem.py +4 -5
  144. prefect/utilities/importtools.py +26 -27
  145. prefect/utilities/pydantic.py +128 -38
  146. prefect/utilities/schema_tools/hydration.py +18 -1
  147. prefect/utilities/schema_tools/validation.py +30 -0
  148. prefect/utilities/services.py +35 -9
  149. prefect/utilities/templating.py +12 -2
  150. prefect/utilities/timeout.py +20 -5
  151. prefect/utilities/urls.py +195 -0
  152. prefect/utilities/visualization.py +1 -0
  153. prefect/variables.py +78 -59
  154. prefect/workers/__init__.py +0 -1
  155. prefect/workers/base.py +237 -244
  156. prefect/workers/block.py +5 -226
  157. prefect/workers/cloud.py +6 -0
  158. prefect/workers/process.py +265 -12
  159. prefect/workers/server.py +29 -11
  160. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/METADATA +28 -24
  161. prefect_client-3.0.0.dist-info/RECORD +201 -0
  162. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/WHEEL +1 -1
  163. prefect/_internal/pydantic/_base_model.py +0 -51
  164. prefect/_internal/pydantic/_compat.py +0 -82
  165. prefect/_internal/pydantic/_flags.py +0 -20
  166. prefect/_internal/pydantic/_types.py +0 -8
  167. prefect/_internal/pydantic/utilities/config_dict.py +0 -72
  168. prefect/_internal/pydantic/utilities/field_validator.py +0 -150
  169. prefect/_internal/pydantic/utilities/model_construct.py +0 -56
  170. prefect/_internal/pydantic/utilities/model_copy.py +0 -55
  171. prefect/_internal/pydantic/utilities/model_dump.py +0 -136
  172. prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
  173. prefect/_internal/pydantic/utilities/model_fields.py +0 -50
  174. prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
  175. prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
  176. prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
  177. prefect/_internal/pydantic/utilities/model_validate.py +0 -75
  178. prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
  179. prefect/_internal/pydantic/utilities/model_validator.py +0 -87
  180. prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
  181. prefect/_vendor/fastapi/__init__.py +0 -25
  182. prefect/_vendor/fastapi/applications.py +0 -946
  183. prefect/_vendor/fastapi/background.py +0 -3
  184. prefect/_vendor/fastapi/concurrency.py +0 -44
  185. prefect/_vendor/fastapi/datastructures.py +0 -58
  186. prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
  187. prefect/_vendor/fastapi/dependencies/models.py +0 -64
  188. prefect/_vendor/fastapi/dependencies/utils.py +0 -877
  189. prefect/_vendor/fastapi/encoders.py +0 -177
  190. prefect/_vendor/fastapi/exception_handlers.py +0 -40
  191. prefect/_vendor/fastapi/exceptions.py +0 -46
  192. prefect/_vendor/fastapi/logger.py +0 -3
  193. prefect/_vendor/fastapi/middleware/__init__.py +0 -1
  194. prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
  195. prefect/_vendor/fastapi/middleware/cors.py +0 -3
  196. prefect/_vendor/fastapi/middleware/gzip.py +0 -3
  197. prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
  198. prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
  199. prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
  200. prefect/_vendor/fastapi/openapi/__init__.py +0 -0
  201. prefect/_vendor/fastapi/openapi/constants.py +0 -2
  202. prefect/_vendor/fastapi/openapi/docs.py +0 -203
  203. prefect/_vendor/fastapi/openapi/models.py +0 -480
  204. prefect/_vendor/fastapi/openapi/utils.py +0 -485
  205. prefect/_vendor/fastapi/param_functions.py +0 -340
  206. prefect/_vendor/fastapi/params.py +0 -453
  207. prefect/_vendor/fastapi/py.typed +0 -0
  208. prefect/_vendor/fastapi/requests.py +0 -4
  209. prefect/_vendor/fastapi/responses.py +0 -40
  210. prefect/_vendor/fastapi/routing.py +0 -1331
  211. prefect/_vendor/fastapi/security/__init__.py +0 -15
  212. prefect/_vendor/fastapi/security/api_key.py +0 -98
  213. prefect/_vendor/fastapi/security/base.py +0 -6
  214. prefect/_vendor/fastapi/security/http.py +0 -172
  215. prefect/_vendor/fastapi/security/oauth2.py +0 -227
  216. prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
  217. prefect/_vendor/fastapi/security/utils.py +0 -10
  218. prefect/_vendor/fastapi/staticfiles.py +0 -1
  219. prefect/_vendor/fastapi/templating.py +0 -3
  220. prefect/_vendor/fastapi/testclient.py +0 -1
  221. prefect/_vendor/fastapi/types.py +0 -3
  222. prefect/_vendor/fastapi/utils.py +0 -235
  223. prefect/_vendor/fastapi/websockets.py +0 -7
  224. prefect/_vendor/starlette/__init__.py +0 -1
  225. prefect/_vendor/starlette/_compat.py +0 -28
  226. prefect/_vendor/starlette/_exception_handler.py +0 -80
  227. prefect/_vendor/starlette/_utils.py +0 -88
  228. prefect/_vendor/starlette/applications.py +0 -261
  229. prefect/_vendor/starlette/authentication.py +0 -159
  230. prefect/_vendor/starlette/background.py +0 -43
  231. prefect/_vendor/starlette/concurrency.py +0 -59
  232. prefect/_vendor/starlette/config.py +0 -151
  233. prefect/_vendor/starlette/convertors.py +0 -87
  234. prefect/_vendor/starlette/datastructures.py +0 -707
  235. prefect/_vendor/starlette/endpoints.py +0 -130
  236. prefect/_vendor/starlette/exceptions.py +0 -60
  237. prefect/_vendor/starlette/formparsers.py +0 -276
  238. prefect/_vendor/starlette/middleware/__init__.py +0 -17
  239. prefect/_vendor/starlette/middleware/authentication.py +0 -52
  240. prefect/_vendor/starlette/middleware/base.py +0 -220
  241. prefect/_vendor/starlette/middleware/cors.py +0 -176
  242. prefect/_vendor/starlette/middleware/errors.py +0 -265
  243. prefect/_vendor/starlette/middleware/exceptions.py +0 -74
  244. prefect/_vendor/starlette/middleware/gzip.py +0 -113
  245. prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
  246. prefect/_vendor/starlette/middleware/sessions.py +0 -82
  247. prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
  248. prefect/_vendor/starlette/middleware/wsgi.py +0 -147
  249. prefect/_vendor/starlette/py.typed +0 -0
  250. prefect/_vendor/starlette/requests.py +0 -328
  251. prefect/_vendor/starlette/responses.py +0 -347
  252. prefect/_vendor/starlette/routing.py +0 -933
  253. prefect/_vendor/starlette/schemas.py +0 -154
  254. prefect/_vendor/starlette/staticfiles.py +0 -248
  255. prefect/_vendor/starlette/status.py +0 -199
  256. prefect/_vendor/starlette/templating.py +0 -231
  257. prefect/_vendor/starlette/testclient.py +0 -804
  258. prefect/_vendor/starlette/types.py +0 -30
  259. prefect/_vendor/starlette/websockets.py +0 -193
  260. prefect/blocks/kubernetes.py +0 -119
  261. prefect/deprecated/__init__.py +0 -0
  262. prefect/deprecated/data_documents.py +0 -350
  263. prefect/deprecated/packaging/__init__.py +0 -12
  264. prefect/deprecated/packaging/base.py +0 -96
  265. prefect/deprecated/packaging/docker.py +0 -146
  266. prefect/deprecated/packaging/file.py +0 -92
  267. prefect/deprecated/packaging/orion.py +0 -80
  268. prefect/deprecated/packaging/serializers.py +0 -171
  269. prefect/events/instrument.py +0 -135
  270. prefect/infrastructure/container.py +0 -824
  271. prefect/infrastructure/kubernetes.py +0 -920
  272. prefect/infrastructure/process.py +0 -289
  273. prefect/manifests.py +0 -20
  274. prefect/new_flow_engine.py +0 -449
  275. prefect/new_task_engine.py +0 -423
  276. prefect/pydantic/__init__.py +0 -76
  277. prefect/pydantic/main.py +0 -39
  278. prefect/software/__init__.py +0 -2
  279. prefect/software/base.py +0 -50
  280. prefect/software/conda.py +0 -199
  281. prefect/software/pip.py +0 -122
  282. prefect/software/python.py +0 -52
  283. prefect/task_server.py +0 -322
  284. prefect_client-2.20.4.dist-info/RECORD +0 -294
  285. /prefect/{_internal/pydantic/utilities → client/types}/__init__.py +0 -0
  286. /prefect/{_vendor → concurrency/v1}/__init__.py +0 -0
  287. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/LICENSE +0 -0
  288. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/top_level.txt +0 -0
@@ -1,220 +0,0 @@
1
- import typing
2
-
3
- import anyio
4
- from anyio.abc import ObjectReceiveStream, ObjectSendStream
5
- from prefect._vendor.starlette._utils import collapse_excgroups
6
- from prefect._vendor.starlette.background import BackgroundTask
7
- from prefect._vendor.starlette.requests import ClientDisconnect, Request
8
- from prefect._vendor.starlette.responses import (
9
- ContentStream,
10
- Response,
11
- StreamingResponse,
12
- )
13
- from prefect._vendor.starlette.types import ASGIApp, Message, Receive, Scope, Send
14
-
15
- RequestResponseEndpoint = typing.Callable[[Request], typing.Awaitable[Response]]
16
- DispatchFunction = typing.Callable[
17
- [Request, RequestResponseEndpoint], typing.Awaitable[Response]
18
- ]
19
- T = typing.TypeVar("T")
20
-
21
-
22
- class _CachedRequest(Request):
23
- """
24
- If the user calls Request.body() from their dispatch function
25
- we cache the entire request body in memory and pass that to downstream middlewares,
26
- but if they call Request.stream() then all we do is send an
27
- empty body so that downstream things don't hang forever.
28
- """
29
-
30
- def __init__(self, scope: Scope, receive: Receive):
31
- super().__init__(scope, receive)
32
- self._wrapped_rcv_disconnected = False
33
- self._wrapped_rcv_consumed = False
34
- self._wrapped_rc_stream = self.stream()
35
-
36
- async def wrapped_receive(self) -> Message:
37
- # wrapped_rcv state 1: disconnected
38
- if self._wrapped_rcv_disconnected:
39
- # we've already sent a disconnect to the downstream app
40
- # we don't need to wait to get another one
41
- # (although most ASGI servers will just keep sending it)
42
- return {"type": "http.disconnect"}
43
- # wrapped_rcv state 1: consumed but not yet disconnected
44
- if self._wrapped_rcv_consumed:
45
- # since the downstream app has consumed us all that is left
46
- # is to send it a disconnect
47
- if self._is_disconnected:
48
- # the middleware has already seen the disconnect
49
- # since we know the client is disconnected no need to wait
50
- # for the message
51
- self._wrapped_rcv_disconnected = True
52
- return {"type": "http.disconnect"}
53
- # we don't know yet if the client is disconnected or not
54
- # so we'll wait until we get that message
55
- msg = await self.receive()
56
- if msg["type"] != "http.disconnect": # pragma: no cover
57
- # at this point a disconnect is all that we should be receiving
58
- # if we get something else, things went wrong somewhere
59
- raise RuntimeError(f"Unexpected message received: {msg['type']}")
60
- return msg
61
-
62
- # wrapped_rcv state 3: not yet consumed
63
- if getattr(self, "_body", None) is not None:
64
- # body() was called, we return it even if the client disconnected
65
- self._wrapped_rcv_consumed = True
66
- return {
67
- "type": "http.request",
68
- "body": self._body,
69
- "more_body": False,
70
- }
71
- elif self._stream_consumed:
72
- # stream() was called to completion
73
- # return an empty body so that downstream apps don't hang
74
- # waiting for a disconnect
75
- self._wrapped_rcv_consumed = True
76
- return {
77
- "type": "http.request",
78
- "body": b"",
79
- "more_body": False,
80
- }
81
- else:
82
- # body() was never called and stream() wasn't consumed
83
- try:
84
- stream = self.stream()
85
- chunk = await stream.__anext__()
86
- self._wrapped_rcv_consumed = self._stream_consumed
87
- return {
88
- "type": "http.request",
89
- "body": chunk,
90
- "more_body": not self._stream_consumed,
91
- }
92
- except ClientDisconnect:
93
- self._wrapped_rcv_disconnected = True
94
- return {"type": "http.disconnect"}
95
-
96
-
97
- class BaseHTTPMiddleware:
98
- def __init__(
99
- self, app: ASGIApp, dispatch: typing.Optional[DispatchFunction] = None
100
- ) -> None:
101
- self.app = app
102
- self.dispatch_func = self.dispatch if dispatch is None else dispatch
103
-
104
- async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
105
- if scope["type"] != "http":
106
- await self.app(scope, receive, send)
107
- return
108
-
109
- request = _CachedRequest(scope, receive)
110
- wrapped_receive = request.wrapped_receive
111
- response_sent = anyio.Event()
112
-
113
- async def call_next(request: Request) -> Response:
114
- app_exc: typing.Optional[Exception] = None
115
- send_stream: ObjectSendStream[typing.MutableMapping[str, typing.Any]]
116
- recv_stream: ObjectReceiveStream[typing.MutableMapping[str, typing.Any]]
117
- send_stream, recv_stream = anyio.create_memory_object_stream()
118
-
119
- async def receive_or_disconnect() -> Message:
120
- if response_sent.is_set():
121
- return {"type": "http.disconnect"}
122
-
123
- async with anyio.create_task_group() as task_group:
124
-
125
- async def wrap(func: typing.Callable[[], typing.Awaitable[T]]) -> T:
126
- result = await func()
127
- task_group.cancel_scope.cancel()
128
- return result
129
-
130
- task_group.start_soon(wrap, response_sent.wait)
131
- message = await wrap(wrapped_receive)
132
-
133
- if response_sent.is_set():
134
- return {"type": "http.disconnect"}
135
-
136
- return message
137
-
138
- async def close_recv_stream_on_response_sent() -> None:
139
- await response_sent.wait()
140
- recv_stream.close()
141
-
142
- async def send_no_error(message: Message) -> None:
143
- try:
144
- await send_stream.send(message)
145
- except anyio.BrokenResourceError:
146
- # recv_stream has been closed, i.e. response_sent has been set.
147
- return
148
-
149
- async def coro() -> None:
150
- nonlocal app_exc
151
-
152
- async with send_stream:
153
- try:
154
- await self.app(scope, receive_or_disconnect, send_no_error)
155
- except Exception as exc:
156
- app_exc = exc
157
-
158
- task_group.start_soon(close_recv_stream_on_response_sent)
159
- task_group.start_soon(coro)
160
-
161
- try:
162
- message = await recv_stream.receive()
163
- info = message.get("info", None)
164
- if message["type"] == "http.response.debug" and info is not None:
165
- message = await recv_stream.receive()
166
- except anyio.EndOfStream:
167
- if app_exc is not None:
168
- raise app_exc
169
- raise RuntimeError("No response returned.")
170
-
171
- assert message["type"] == "http.response.start"
172
-
173
- async def body_stream() -> typing.AsyncGenerator[bytes, None]:
174
- async with recv_stream:
175
- async for message in recv_stream:
176
- assert message["type"] == "http.response.body"
177
- body = message.get("body", b"")
178
- if body:
179
- yield body
180
- if not message.get("more_body", False):
181
- break
182
-
183
- if app_exc is not None:
184
- raise app_exc
185
-
186
- response = _StreamingResponse(
187
- status_code=message["status"], content=body_stream(), info=info
188
- )
189
- response.raw_headers = message["headers"]
190
- return response
191
-
192
- with collapse_excgroups():
193
- async with anyio.create_task_group() as task_group:
194
- response = await self.dispatch_func(request, call_next)
195
- await response(scope, wrapped_receive, send)
196
- response_sent.set()
197
-
198
- async def dispatch(
199
- self, request: Request, call_next: RequestResponseEndpoint
200
- ) -> Response:
201
- raise NotImplementedError() # pragma: no cover
202
-
203
-
204
- class _StreamingResponse(StreamingResponse):
205
- def __init__(
206
- self,
207
- content: ContentStream,
208
- status_code: int = 200,
209
- headers: typing.Optional[typing.Mapping[str, str]] = None,
210
- media_type: typing.Optional[str] = None,
211
- background: typing.Optional[BackgroundTask] = None,
212
- info: typing.Optional[typing.Mapping[str, typing.Any]] = None,
213
- ) -> None:
214
- self._info = info
215
- super().__init__(content, status_code, headers, media_type, background)
216
-
217
- async def stream_response(self, send: Send) -> None:
218
- if self._info:
219
- await send({"type": "http.response.debug", "info": self._info})
220
- return await super().stream_response(send)
@@ -1,176 +0,0 @@
1
- import functools
2
- import re
3
- import typing
4
-
5
- from prefect._vendor.starlette.datastructures import Headers, MutableHeaders
6
- from prefect._vendor.starlette.responses import PlainTextResponse, Response
7
- from prefect._vendor.starlette.types import ASGIApp, Message, Receive, Scope, Send
8
-
9
- ALL_METHODS = ("DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT")
10
- SAFELISTED_HEADERS = {"Accept", "Accept-Language", "Content-Language", "Content-Type"}
11
-
12
-
13
- class CORSMiddleware:
14
- def __init__(
15
- self,
16
- app: ASGIApp,
17
- allow_origins: typing.Sequence[str] = (),
18
- allow_methods: typing.Sequence[str] = ("GET",),
19
- allow_headers: typing.Sequence[str] = (),
20
- allow_credentials: bool = False,
21
- allow_origin_regex: typing.Optional[str] = None,
22
- expose_headers: typing.Sequence[str] = (),
23
- max_age: int = 600,
24
- ) -> None:
25
- if "*" in allow_methods:
26
- allow_methods = ALL_METHODS
27
-
28
- compiled_allow_origin_regex = None
29
- if allow_origin_regex is not None:
30
- compiled_allow_origin_regex = re.compile(allow_origin_regex)
31
-
32
- allow_all_origins = "*" in allow_origins
33
- allow_all_headers = "*" in allow_headers
34
- preflight_explicit_allow_origin = not allow_all_origins or allow_credentials
35
-
36
- simple_headers = {}
37
- if allow_all_origins:
38
- simple_headers["Access-Control-Allow-Origin"] = "*"
39
- if allow_credentials:
40
- simple_headers["Access-Control-Allow-Credentials"] = "true"
41
- if expose_headers:
42
- simple_headers["Access-Control-Expose-Headers"] = ", ".join(expose_headers)
43
-
44
- preflight_headers = {}
45
- if preflight_explicit_allow_origin:
46
- # The origin value will be set in preflight_response() if it is allowed.
47
- preflight_headers["Vary"] = "Origin"
48
- else:
49
- preflight_headers["Access-Control-Allow-Origin"] = "*"
50
- preflight_headers.update(
51
- {
52
- "Access-Control-Allow-Methods": ", ".join(allow_methods),
53
- "Access-Control-Max-Age": str(max_age),
54
- }
55
- )
56
- allow_headers = sorted(SAFELISTED_HEADERS | set(allow_headers))
57
- if allow_headers and not allow_all_headers:
58
- preflight_headers["Access-Control-Allow-Headers"] = ", ".join(allow_headers)
59
- if allow_credentials:
60
- preflight_headers["Access-Control-Allow-Credentials"] = "true"
61
-
62
- self.app = app
63
- self.allow_origins = allow_origins
64
- self.allow_methods = allow_methods
65
- self.allow_headers = [h.lower() for h in allow_headers]
66
- self.allow_all_origins = allow_all_origins
67
- self.allow_all_headers = allow_all_headers
68
- self.preflight_explicit_allow_origin = preflight_explicit_allow_origin
69
- self.allow_origin_regex = compiled_allow_origin_regex
70
- self.simple_headers = simple_headers
71
- self.preflight_headers = preflight_headers
72
-
73
- async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
74
- if scope["type"] != "http": # pragma: no cover
75
- await self.app(scope, receive, send)
76
- return
77
-
78
- method = scope["method"]
79
- headers = Headers(scope=scope)
80
- origin = headers.get("origin")
81
-
82
- if origin is None:
83
- await self.app(scope, receive, send)
84
- return
85
-
86
- if method == "OPTIONS" and "access-control-request-method" in headers:
87
- response = self.preflight_response(request_headers=headers)
88
- await response(scope, receive, send)
89
- return
90
-
91
- await self.simple_response(scope, receive, send, request_headers=headers)
92
-
93
- def is_allowed_origin(self, origin: str) -> bool:
94
- if self.allow_all_origins:
95
- return True
96
-
97
- if self.allow_origin_regex is not None and self.allow_origin_regex.fullmatch(
98
- origin
99
- ):
100
- return True
101
-
102
- return origin in self.allow_origins
103
-
104
- def preflight_response(self, request_headers: Headers) -> Response:
105
- requested_origin = request_headers["origin"]
106
- requested_method = request_headers["access-control-request-method"]
107
- requested_headers = request_headers.get("access-control-request-headers")
108
-
109
- headers = dict(self.preflight_headers)
110
- failures = []
111
-
112
- if self.is_allowed_origin(origin=requested_origin):
113
- if self.preflight_explicit_allow_origin:
114
- # The "else" case is already accounted for in self.preflight_headers
115
- # and the value would be "*".
116
- headers["Access-Control-Allow-Origin"] = requested_origin
117
- else:
118
- failures.append("origin")
119
-
120
- if requested_method not in self.allow_methods:
121
- failures.append("method")
122
-
123
- # If we allow all headers, then we have to mirror back any requested
124
- # headers in the response.
125
- if self.allow_all_headers and requested_headers is not None:
126
- headers["Access-Control-Allow-Headers"] = requested_headers
127
- elif requested_headers is not None:
128
- for header in [h.lower() for h in requested_headers.split(",")]:
129
- if header.strip() not in self.allow_headers:
130
- failures.append("headers")
131
- break
132
-
133
- # We don't strictly need to use 400 responses here, since its up to
134
- # the browser to enforce the CORS policy, but its more informative
135
- # if we do.
136
- if failures:
137
- failure_text = "Disallowed CORS " + ", ".join(failures)
138
- return PlainTextResponse(failure_text, status_code=400, headers=headers)
139
-
140
- return PlainTextResponse("OK", status_code=200, headers=headers)
141
-
142
- async def simple_response(
143
- self, scope: Scope, receive: Receive, send: Send, request_headers: Headers
144
- ) -> None:
145
- send = functools.partial(self.send, send=send, request_headers=request_headers)
146
- await self.app(scope, receive, send)
147
-
148
- async def send(
149
- self, message: Message, send: Send, request_headers: Headers
150
- ) -> None:
151
- if message["type"] != "http.response.start":
152
- await send(message)
153
- return
154
-
155
- message.setdefault("headers", [])
156
- headers = MutableHeaders(scope=message)
157
- headers.update(self.simple_headers)
158
- origin = request_headers["Origin"]
159
- has_cookie = "cookie" in request_headers
160
-
161
- # If request includes any cookie headers, then we must respond
162
- # with the specific origin instead of '*'.
163
- if self.allow_all_origins and has_cookie:
164
- self.allow_explicit_origin(headers, origin)
165
-
166
- # If we only allow specific origins, then we have to mirror back
167
- # the Origin header in the response.
168
- elif not self.allow_all_origins and self.is_allowed_origin(origin=origin):
169
- self.allow_explicit_origin(headers, origin)
170
-
171
- await send(message)
172
-
173
- @staticmethod
174
- def allow_explicit_origin(headers: MutableHeaders, origin: str) -> None:
175
- headers["Access-Control-Allow-Origin"] = origin
176
- headers.add_vary_header("Origin")
@@ -1,265 +0,0 @@
1
- import html
2
- import inspect
3
- import traceback
4
- import typing
5
-
6
- from prefect._vendor.starlette._utils import is_async_callable
7
- from prefect._vendor.starlette.concurrency import run_in_threadpool
8
- from prefect._vendor.starlette.requests import Request
9
- from prefect._vendor.starlette.responses import (
10
- HTMLResponse,
11
- PlainTextResponse,
12
- Response,
13
- )
14
- from prefect._vendor.starlette.types import ASGIApp, Message, Receive, Scope, Send
15
-
16
- STYLES = """
17
- p {
18
- color: #211c1c;
19
- }
20
- .traceback-container {
21
- border: 1px solid #038BB8;
22
- }
23
- .traceback-title {
24
- background-color: #038BB8;
25
- color: lemonchiffon;
26
- padding: 12px;
27
- font-size: 20px;
28
- margin-top: 0px;
29
- }
30
- .frame-line {
31
- padding-left: 10px;
32
- font-family: monospace;
33
- }
34
- .frame-filename {
35
- font-family: monospace;
36
- }
37
- .center-line {
38
- background-color: #038BB8;
39
- color: #f9f6e1;
40
- padding: 5px 0px 5px 5px;
41
- }
42
- .lineno {
43
- margin-right: 5px;
44
- }
45
- .frame-title {
46
- font-weight: unset;
47
- padding: 10px 10px 10px 10px;
48
- background-color: #E4F4FD;
49
- margin-right: 10px;
50
- color: #191f21;
51
- font-size: 17px;
52
- border: 1px solid #c7dce8;
53
- }
54
- .collapse-btn {
55
- float: right;
56
- padding: 0px 5px 1px 5px;
57
- border: solid 1px #96aebb;
58
- cursor: pointer;
59
- }
60
- .collapsed {
61
- display: none;
62
- }
63
- .source-code {
64
- font-family: courier;
65
- font-size: small;
66
- padding-bottom: 10px;
67
- }
68
- """
69
-
70
- JS = """
71
- <script type="text/javascript">
72
- function collapse(element){
73
- const frameId = element.getAttribute("data-frame-id");
74
- const frame = document.getElementById(frameId);
75
-
76
- if (frame.classList.contains("collapsed")){
77
- element.innerHTML = "&#8210;";
78
- frame.classList.remove("collapsed");
79
- } else {
80
- element.innerHTML = "+";
81
- frame.classList.add("collapsed");
82
- }
83
- }
84
- </script>
85
- """
86
-
87
- TEMPLATE = """
88
- <html>
89
- <head>
90
- <style type='text/css'>
91
- {styles}
92
- </style>
93
- <title>Starlette Debugger</title>
94
- </head>
95
- <body>
96
- <h1>500 Server Error</h1>
97
- <h2>{error}</h2>
98
- <div class="traceback-container">
99
- <p class="traceback-title">Traceback</p>
100
- <div>{exc_html}</div>
101
- </div>
102
- {js}
103
- </body>
104
- </html>
105
- """
106
-
107
- FRAME_TEMPLATE = """
108
- <div>
109
- <p class="frame-title">File <span class="frame-filename">{frame_filename}</span>,
110
- line <i>{frame_lineno}</i>,
111
- in <b>{frame_name}</b>
112
- <span class="collapse-btn" data-frame-id="{frame_filename}-{frame_lineno}" onclick="collapse(this)">{collapse_button}</span>
113
- </p>
114
- <div id="{frame_filename}-{frame_lineno}" class="source-code {collapsed}">{code_context}</div>
115
- </div>
116
- """ # noqa: E501
117
-
118
- LINE = """
119
- <p><span class="frame-line">
120
- <span class="lineno">{lineno}.</span> {line}</span></p>
121
- """
122
-
123
- CENTER_LINE = """
124
- <p class="center-line"><span class="frame-line center-line">
125
- <span class="lineno">{lineno}.</span> {line}</span></p>
126
- """
127
-
128
-
129
- class ServerErrorMiddleware:
130
- """
131
- Handles returning 500 responses when a server error occurs.
132
-
133
- If 'debug' is set, then traceback responses will be returned,
134
- otherwise the designated 'handler' will be called.
135
-
136
- This middleware class should generally be used to wrap *everything*
137
- else up, so that unhandled exceptions anywhere in the stack
138
- always result in an appropriate 500 response.
139
- """
140
-
141
- def __init__(
142
- self,
143
- app: ASGIApp,
144
- handler: typing.Optional[
145
- typing.Callable[[Request, Exception], typing.Any]
146
- ] = None,
147
- debug: bool = False,
148
- ) -> None:
149
- self.app = app
150
- self.handler = handler
151
- self.debug = debug
152
-
153
- async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
154
- if scope["type"] != "http":
155
- await self.app(scope, receive, send)
156
- return
157
-
158
- response_started = False
159
-
160
- async def _send(message: Message) -> None:
161
- nonlocal response_started, send
162
-
163
- if message["type"] == "http.response.start":
164
- response_started = True
165
- await send(message)
166
-
167
- try:
168
- await self.app(scope, receive, _send)
169
- except Exception as exc:
170
- request = Request(scope)
171
- if self.debug:
172
- # In debug mode, return traceback responses.
173
- response = self.debug_response(request, exc)
174
- elif self.handler is None:
175
- # Use our default 500 error handler.
176
- response = self.error_response(request, exc)
177
- else:
178
- # Use an installed 500 error handler.
179
- if is_async_callable(self.handler):
180
- response = await self.handler(request, exc)
181
- else:
182
- response = await run_in_threadpool(self.handler, request, exc)
183
-
184
- if not response_started:
185
- await response(scope, receive, send)
186
-
187
- # We always continue to raise the exception.
188
- # This allows servers to log the error, or allows test clients
189
- # to optionally raise the error within the test case.
190
- raise exc
191
-
192
- def format_line(
193
- self, index: int, line: str, frame_lineno: int, frame_index: int
194
- ) -> str:
195
- values = {
196
- # HTML escape - line could contain < or >
197
- "line": html.escape(line).replace(" ", "&nbsp"),
198
- "lineno": (frame_lineno - frame_index) + index,
199
- }
200
-
201
- if index != frame_index:
202
- return LINE.format(**values)
203
- return CENTER_LINE.format(**values)
204
-
205
- def generate_frame_html(self, frame: inspect.FrameInfo, is_collapsed: bool) -> str:
206
- code_context = "".join(
207
- self.format_line(
208
- index,
209
- line,
210
- frame.lineno,
211
- frame.index, # type: ignore[arg-type]
212
- )
213
- for index, line in enumerate(frame.code_context or [])
214
- )
215
-
216
- values = {
217
- # HTML escape - filename could contain < or >, especially if it's a virtual
218
- # file e.g. <stdin> in the REPL
219
- "frame_filename": html.escape(frame.filename),
220
- "frame_lineno": frame.lineno,
221
- # HTML escape - if you try very hard it's possible to name a function with <
222
- # or >
223
- "frame_name": html.escape(frame.function),
224
- "code_context": code_context,
225
- "collapsed": "collapsed" if is_collapsed else "",
226
- "collapse_button": "+" if is_collapsed else "&#8210;",
227
- }
228
- return FRAME_TEMPLATE.format(**values)
229
-
230
- def generate_html(self, exc: Exception, limit: int = 7) -> str:
231
- traceback_obj = traceback.TracebackException.from_exception(
232
- exc, capture_locals=True
233
- )
234
-
235
- exc_html = ""
236
- is_collapsed = False
237
- exc_traceback = exc.__traceback__
238
- if exc_traceback is not None:
239
- frames = inspect.getinnerframes(exc_traceback, limit)
240
- for frame in reversed(frames):
241
- exc_html += self.generate_frame_html(frame, is_collapsed)
242
- is_collapsed = True
243
-
244
- # escape error class and text
245
- error = (
246
- f"{html.escape(traceback_obj.exc_type.__name__)}: "
247
- f"{html.escape(str(traceback_obj))}"
248
- )
249
-
250
- return TEMPLATE.format(styles=STYLES, js=JS, error=error, exc_html=exc_html)
251
-
252
- def generate_plain_text(self, exc: Exception) -> str:
253
- return "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
254
-
255
- def debug_response(self, request: Request, exc: Exception) -> Response:
256
- accept = request.headers.get("accept", "")
257
-
258
- if "text/html" in accept:
259
- content = self.generate_html(exc)
260
- return HTMLResponse(content, status_code=500)
261
- content = self.generate_plain_text(exc)
262
- return PlainTextResponse(content, status_code=500)
263
-
264
- def error_response(self, request: Request, exc: Exception) -> Response:
265
- return PlainTextResponse("Internal Server Error", status_code=500)