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.
- prefect/__init__.py +74 -110
- prefect/_internal/compatibility/deprecated.py +6 -115
- prefect/_internal/compatibility/experimental.py +4 -79
- prefect/_internal/compatibility/migration.py +166 -0
- prefect/_internal/concurrency/__init__.py +2 -2
- prefect/_internal/concurrency/api.py +1 -35
- prefect/_internal/concurrency/calls.py +0 -6
- prefect/_internal/concurrency/cancellation.py +0 -3
- prefect/_internal/concurrency/event_loop.py +0 -20
- prefect/_internal/concurrency/inspection.py +3 -3
- prefect/_internal/concurrency/primitives.py +1 -0
- prefect/_internal/concurrency/services.py +23 -0
- prefect/_internal/concurrency/threads.py +35 -0
- prefect/_internal/concurrency/waiters.py +0 -28
- prefect/_internal/integrations.py +7 -0
- prefect/_internal/pydantic/__init__.py +0 -45
- prefect/_internal/pydantic/annotations/pendulum.py +2 -2
- prefect/_internal/pydantic/v1_schema.py +21 -22
- prefect/_internal/pydantic/v2_schema.py +0 -2
- prefect/_internal/pydantic/v2_validated_func.py +18 -23
- prefect/_internal/pytz.py +1 -1
- prefect/_internal/retries.py +61 -0
- prefect/_internal/schemas/bases.py +45 -177
- prefect/_internal/schemas/fields.py +1 -43
- prefect/_internal/schemas/validators.py +47 -233
- prefect/agent.py +3 -695
- prefect/artifacts.py +173 -14
- prefect/automations.py +39 -4
- prefect/blocks/abstract.py +1 -1
- prefect/blocks/core.py +405 -153
- prefect/blocks/fields.py +2 -57
- prefect/blocks/notifications.py +43 -28
- prefect/blocks/redis.py +168 -0
- prefect/blocks/system.py +67 -20
- prefect/blocks/webhook.py +2 -9
- prefect/cache_policies.py +239 -0
- prefect/client/__init__.py +4 -0
- prefect/client/base.py +33 -27
- prefect/client/cloud.py +65 -20
- prefect/client/collections.py +1 -1
- prefect/client/orchestration.py +650 -442
- prefect/client/schemas/actions.py +115 -100
- prefect/client/schemas/filters.py +46 -52
- prefect/client/schemas/objects.py +228 -178
- prefect/client/schemas/responses.py +18 -36
- prefect/client/schemas/schedules.py +55 -36
- prefect/client/schemas/sorting.py +2 -0
- prefect/client/subscriptions.py +8 -7
- prefect/client/types/flexible_schedule_list.py +11 -0
- prefect/client/utilities.py +9 -6
- prefect/concurrency/asyncio.py +60 -11
- prefect/concurrency/context.py +24 -0
- prefect/concurrency/events.py +2 -2
- prefect/concurrency/services.py +46 -16
- prefect/concurrency/sync.py +51 -7
- prefect/concurrency/v1/asyncio.py +143 -0
- prefect/concurrency/v1/context.py +27 -0
- prefect/concurrency/v1/events.py +61 -0
- prefect/concurrency/v1/services.py +116 -0
- prefect/concurrency/v1/sync.py +92 -0
- prefect/context.py +246 -149
- prefect/deployments/__init__.py +33 -18
- prefect/deployments/base.py +10 -15
- prefect/deployments/deployments.py +2 -1048
- prefect/deployments/flow_runs.py +178 -0
- prefect/deployments/runner.py +72 -173
- prefect/deployments/schedules.py +31 -25
- prefect/deployments/steps/__init__.py +0 -1
- prefect/deployments/steps/core.py +7 -0
- prefect/deployments/steps/pull.py +15 -21
- prefect/deployments/steps/utility.py +2 -1
- prefect/docker/__init__.py +20 -0
- prefect/docker/docker_image.py +82 -0
- prefect/engine.py +15 -2475
- prefect/events/actions.py +17 -23
- prefect/events/cli/automations.py +20 -7
- prefect/events/clients.py +142 -80
- prefect/events/filters.py +14 -18
- prefect/events/related.py +74 -75
- prefect/events/schemas/__init__.py +0 -5
- prefect/events/schemas/automations.py +55 -46
- prefect/events/schemas/deployment_triggers.py +7 -197
- prefect/events/schemas/events.py +46 -65
- prefect/events/schemas/labelling.py +10 -14
- prefect/events/utilities.py +4 -5
- prefect/events/worker.py +23 -8
- prefect/exceptions.py +15 -0
- prefect/filesystems.py +30 -529
- prefect/flow_engine.py +827 -0
- prefect/flow_runs.py +379 -7
- prefect/flows.py +470 -360
- prefect/futures.py +382 -331
- prefect/infrastructure/__init__.py +5 -26
- prefect/infrastructure/base.py +3 -320
- prefect/infrastructure/provisioners/__init__.py +5 -3
- prefect/infrastructure/provisioners/cloud_run.py +13 -8
- prefect/infrastructure/provisioners/container_instance.py +14 -9
- prefect/infrastructure/provisioners/ecs.py +10 -8
- prefect/infrastructure/provisioners/modal.py +8 -5
- prefect/input/__init__.py +4 -0
- prefect/input/actions.py +2 -4
- prefect/input/run_input.py +9 -9
- prefect/logging/formatters.py +2 -4
- prefect/logging/handlers.py +9 -14
- prefect/logging/loggers.py +5 -5
- prefect/main.py +72 -0
- prefect/plugins.py +2 -64
- prefect/profiles.toml +16 -2
- prefect/records/__init__.py +1 -0
- prefect/records/base.py +223 -0
- prefect/records/filesystem.py +207 -0
- prefect/records/memory.py +178 -0
- prefect/records/result_store.py +64 -0
- prefect/results.py +577 -504
- prefect/runner/runner.py +117 -47
- prefect/runner/server.py +32 -34
- prefect/runner/storage.py +3 -12
- prefect/runner/submit.py +2 -10
- prefect/runner/utils.py +2 -2
- prefect/runtime/__init__.py +1 -0
- prefect/runtime/deployment.py +1 -0
- prefect/runtime/flow_run.py +40 -5
- prefect/runtime/task_run.py +1 -0
- prefect/serializers.py +28 -39
- prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
- prefect/settings.py +209 -332
- prefect/states.py +160 -63
- prefect/task_engine.py +1478 -57
- prefect/task_runners.py +383 -287
- prefect/task_runs.py +240 -0
- prefect/task_worker.py +463 -0
- prefect/tasks.py +684 -374
- prefect/transactions.py +410 -0
- prefect/types/__init__.py +72 -86
- prefect/types/entrypoint.py +13 -0
- prefect/utilities/annotations.py +4 -3
- prefect/utilities/asyncutils.py +227 -148
- prefect/utilities/callables.py +137 -45
- prefect/utilities/collections.py +134 -86
- prefect/utilities/dispatch.py +27 -14
- prefect/utilities/dockerutils.py +11 -4
- prefect/utilities/engine.py +186 -32
- prefect/utilities/filesystem.py +4 -5
- prefect/utilities/importtools.py +26 -27
- prefect/utilities/pydantic.py +128 -38
- prefect/utilities/schema_tools/hydration.py +18 -1
- prefect/utilities/schema_tools/validation.py +30 -0
- prefect/utilities/services.py +35 -9
- prefect/utilities/templating.py +12 -2
- prefect/utilities/timeout.py +20 -5
- prefect/utilities/urls.py +195 -0
- prefect/utilities/visualization.py +1 -0
- prefect/variables.py +78 -59
- prefect/workers/__init__.py +0 -1
- prefect/workers/base.py +237 -244
- prefect/workers/block.py +5 -226
- prefect/workers/cloud.py +6 -0
- prefect/workers/process.py +265 -12
- prefect/workers/server.py +29 -11
- {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/METADATA +28 -24
- prefect_client-3.0.0.dist-info/RECORD +201 -0
- {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/WHEEL +1 -1
- prefect/_internal/pydantic/_base_model.py +0 -51
- prefect/_internal/pydantic/_compat.py +0 -82
- prefect/_internal/pydantic/_flags.py +0 -20
- prefect/_internal/pydantic/_types.py +0 -8
- prefect/_internal/pydantic/utilities/config_dict.py +0 -72
- prefect/_internal/pydantic/utilities/field_validator.py +0 -150
- prefect/_internal/pydantic/utilities/model_construct.py +0 -56
- prefect/_internal/pydantic/utilities/model_copy.py +0 -55
- prefect/_internal/pydantic/utilities/model_dump.py +0 -136
- prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
- prefect/_internal/pydantic/utilities/model_fields.py +0 -50
- prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
- prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
- prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
- prefect/_internal/pydantic/utilities/model_validate.py +0 -75
- prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
- prefect/_internal/pydantic/utilities/model_validator.py +0 -87
- prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
- prefect/_vendor/fastapi/__init__.py +0 -25
- prefect/_vendor/fastapi/applications.py +0 -946
- prefect/_vendor/fastapi/background.py +0 -3
- prefect/_vendor/fastapi/concurrency.py +0 -44
- prefect/_vendor/fastapi/datastructures.py +0 -58
- prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
- prefect/_vendor/fastapi/dependencies/models.py +0 -64
- prefect/_vendor/fastapi/dependencies/utils.py +0 -877
- prefect/_vendor/fastapi/encoders.py +0 -177
- prefect/_vendor/fastapi/exception_handlers.py +0 -40
- prefect/_vendor/fastapi/exceptions.py +0 -46
- prefect/_vendor/fastapi/logger.py +0 -3
- prefect/_vendor/fastapi/middleware/__init__.py +0 -1
- prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
- prefect/_vendor/fastapi/middleware/cors.py +0 -3
- prefect/_vendor/fastapi/middleware/gzip.py +0 -3
- prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
- prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
- prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
- prefect/_vendor/fastapi/openapi/__init__.py +0 -0
- prefect/_vendor/fastapi/openapi/constants.py +0 -2
- prefect/_vendor/fastapi/openapi/docs.py +0 -203
- prefect/_vendor/fastapi/openapi/models.py +0 -480
- prefect/_vendor/fastapi/openapi/utils.py +0 -485
- prefect/_vendor/fastapi/param_functions.py +0 -340
- prefect/_vendor/fastapi/params.py +0 -453
- prefect/_vendor/fastapi/py.typed +0 -0
- prefect/_vendor/fastapi/requests.py +0 -4
- prefect/_vendor/fastapi/responses.py +0 -40
- prefect/_vendor/fastapi/routing.py +0 -1331
- prefect/_vendor/fastapi/security/__init__.py +0 -15
- prefect/_vendor/fastapi/security/api_key.py +0 -98
- prefect/_vendor/fastapi/security/base.py +0 -6
- prefect/_vendor/fastapi/security/http.py +0 -172
- prefect/_vendor/fastapi/security/oauth2.py +0 -227
- prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
- prefect/_vendor/fastapi/security/utils.py +0 -10
- prefect/_vendor/fastapi/staticfiles.py +0 -1
- prefect/_vendor/fastapi/templating.py +0 -3
- prefect/_vendor/fastapi/testclient.py +0 -1
- prefect/_vendor/fastapi/types.py +0 -3
- prefect/_vendor/fastapi/utils.py +0 -235
- prefect/_vendor/fastapi/websockets.py +0 -7
- prefect/_vendor/starlette/__init__.py +0 -1
- prefect/_vendor/starlette/_compat.py +0 -28
- prefect/_vendor/starlette/_exception_handler.py +0 -80
- prefect/_vendor/starlette/_utils.py +0 -88
- prefect/_vendor/starlette/applications.py +0 -261
- prefect/_vendor/starlette/authentication.py +0 -159
- prefect/_vendor/starlette/background.py +0 -43
- prefect/_vendor/starlette/concurrency.py +0 -59
- prefect/_vendor/starlette/config.py +0 -151
- prefect/_vendor/starlette/convertors.py +0 -87
- prefect/_vendor/starlette/datastructures.py +0 -707
- prefect/_vendor/starlette/endpoints.py +0 -130
- prefect/_vendor/starlette/exceptions.py +0 -60
- prefect/_vendor/starlette/formparsers.py +0 -276
- prefect/_vendor/starlette/middleware/__init__.py +0 -17
- prefect/_vendor/starlette/middleware/authentication.py +0 -52
- prefect/_vendor/starlette/middleware/base.py +0 -220
- prefect/_vendor/starlette/middleware/cors.py +0 -176
- prefect/_vendor/starlette/middleware/errors.py +0 -265
- prefect/_vendor/starlette/middleware/exceptions.py +0 -74
- prefect/_vendor/starlette/middleware/gzip.py +0 -113
- prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
- prefect/_vendor/starlette/middleware/sessions.py +0 -82
- prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
- prefect/_vendor/starlette/middleware/wsgi.py +0 -147
- prefect/_vendor/starlette/py.typed +0 -0
- prefect/_vendor/starlette/requests.py +0 -328
- prefect/_vendor/starlette/responses.py +0 -347
- prefect/_vendor/starlette/routing.py +0 -933
- prefect/_vendor/starlette/schemas.py +0 -154
- prefect/_vendor/starlette/staticfiles.py +0 -248
- prefect/_vendor/starlette/status.py +0 -199
- prefect/_vendor/starlette/templating.py +0 -231
- prefect/_vendor/starlette/testclient.py +0 -804
- prefect/_vendor/starlette/types.py +0 -30
- prefect/_vendor/starlette/websockets.py +0 -193
- prefect/blocks/kubernetes.py +0 -119
- prefect/deprecated/__init__.py +0 -0
- prefect/deprecated/data_documents.py +0 -350
- prefect/deprecated/packaging/__init__.py +0 -12
- prefect/deprecated/packaging/base.py +0 -96
- prefect/deprecated/packaging/docker.py +0 -146
- prefect/deprecated/packaging/file.py +0 -92
- prefect/deprecated/packaging/orion.py +0 -80
- prefect/deprecated/packaging/serializers.py +0 -171
- prefect/events/instrument.py +0 -135
- prefect/infrastructure/container.py +0 -824
- prefect/infrastructure/kubernetes.py +0 -920
- prefect/infrastructure/process.py +0 -289
- prefect/manifests.py +0 -20
- prefect/new_flow_engine.py +0 -449
- prefect/new_task_engine.py +0 -423
- prefect/pydantic/__init__.py +0 -76
- prefect/pydantic/main.py +0 -39
- prefect/software/__init__.py +0 -2
- prefect/software/base.py +0 -50
- prefect/software/conda.py +0 -199
- prefect/software/pip.py +0 -122
- prefect/software/python.py +0 -52
- prefect/task_server.py +0 -322
- prefect_client-2.20.4.dist-info/RECORD +0 -294
- /prefect/{_internal/pydantic/utilities → client/types}/__init__.py +0 -0
- /prefect/{_vendor → concurrency/v1}/__init__.py +0 -0
- {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/LICENSE +0 -0
- {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 = "‒";
|
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(" ", " "),
|
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 "‒",
|
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)
|