prefect-client 2.14.21__py3-none-any.whl → 2.15.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 (103) hide show
  1. prefect/_internal/concurrency/api.py +37 -2
  2. prefect/_internal/concurrency/calls.py +9 -0
  3. prefect/_internal/concurrency/cancellation.py +3 -1
  4. prefect/_internal/concurrency/event_loop.py +2 -2
  5. prefect/_internal/concurrency/threads.py +3 -2
  6. prefect/_internal/pydantic/annotations/pendulum.py +4 -4
  7. prefect/_internal/pydantic/v2_schema.py +2 -2
  8. prefect/_vendor/fastapi/__init__.py +1 -1
  9. prefect/_vendor/fastapi/applications.py +13 -13
  10. prefect/_vendor/fastapi/background.py +3 -1
  11. prefect/_vendor/fastapi/concurrency.py +7 -3
  12. prefect/_vendor/fastapi/datastructures.py +9 -7
  13. prefect/_vendor/fastapi/dependencies/utils.py +12 -7
  14. prefect/_vendor/fastapi/encoders.py +1 -1
  15. prefect/_vendor/fastapi/exception_handlers.py +7 -4
  16. prefect/_vendor/fastapi/exceptions.py +4 -2
  17. prefect/_vendor/fastapi/middleware/__init__.py +1 -1
  18. prefect/_vendor/fastapi/middleware/asyncexitstack.py +1 -1
  19. prefect/_vendor/fastapi/middleware/cors.py +3 -1
  20. prefect/_vendor/fastapi/middleware/gzip.py +3 -1
  21. prefect/_vendor/fastapi/middleware/httpsredirect.py +1 -1
  22. prefect/_vendor/fastapi/middleware/trustedhost.py +1 -1
  23. prefect/_vendor/fastapi/middleware/wsgi.py +3 -1
  24. prefect/_vendor/fastapi/openapi/docs.py +1 -1
  25. prefect/_vendor/fastapi/openapi/utils.py +3 -3
  26. prefect/_vendor/fastapi/requests.py +4 -2
  27. prefect/_vendor/fastapi/responses.py +13 -7
  28. prefect/_vendor/fastapi/routing.py +15 -15
  29. prefect/_vendor/fastapi/security/api_key.py +3 -3
  30. prefect/_vendor/fastapi/security/http.py +2 -2
  31. prefect/_vendor/fastapi/security/oauth2.py +2 -2
  32. prefect/_vendor/fastapi/security/open_id_connect_url.py +3 -3
  33. prefect/_vendor/fastapi/staticfiles.py +1 -1
  34. prefect/_vendor/fastapi/templating.py +3 -1
  35. prefect/_vendor/fastapi/testclient.py +1 -1
  36. prefect/_vendor/fastapi/utils.py +3 -3
  37. prefect/_vendor/fastapi/websockets.py +7 -3
  38. prefect/_vendor/starlette/__init__.py +1 -0
  39. prefect/_vendor/starlette/_compat.py +28 -0
  40. prefect/_vendor/starlette/_exception_handler.py +80 -0
  41. prefect/_vendor/starlette/_utils.py +88 -0
  42. prefect/_vendor/starlette/applications.py +261 -0
  43. prefect/_vendor/starlette/authentication.py +159 -0
  44. prefect/_vendor/starlette/background.py +43 -0
  45. prefect/_vendor/starlette/concurrency.py +59 -0
  46. prefect/_vendor/starlette/config.py +151 -0
  47. prefect/_vendor/starlette/convertors.py +87 -0
  48. prefect/_vendor/starlette/datastructures.py +707 -0
  49. prefect/_vendor/starlette/endpoints.py +130 -0
  50. prefect/_vendor/starlette/exceptions.py +60 -0
  51. prefect/_vendor/starlette/formparsers.py +276 -0
  52. prefect/_vendor/starlette/middleware/__init__.py +17 -0
  53. prefect/_vendor/starlette/middleware/authentication.py +52 -0
  54. prefect/_vendor/starlette/middleware/base.py +220 -0
  55. prefect/_vendor/starlette/middleware/cors.py +176 -0
  56. prefect/_vendor/starlette/middleware/errors.py +265 -0
  57. prefect/_vendor/starlette/middleware/exceptions.py +74 -0
  58. prefect/_vendor/starlette/middleware/gzip.py +113 -0
  59. prefect/_vendor/starlette/middleware/httpsredirect.py +19 -0
  60. prefect/_vendor/starlette/middleware/sessions.py +82 -0
  61. prefect/_vendor/starlette/middleware/trustedhost.py +64 -0
  62. prefect/_vendor/starlette/middleware/wsgi.py +147 -0
  63. prefect/_vendor/starlette/requests.py +328 -0
  64. prefect/_vendor/starlette/responses.py +347 -0
  65. prefect/_vendor/starlette/routing.py +933 -0
  66. prefect/_vendor/starlette/schemas.py +154 -0
  67. prefect/_vendor/starlette/staticfiles.py +248 -0
  68. prefect/_vendor/starlette/status.py +199 -0
  69. prefect/_vendor/starlette/templating.py +231 -0
  70. prefect/_vendor/starlette/testclient.py +805 -0
  71. prefect/_vendor/starlette/types.py +30 -0
  72. prefect/_vendor/starlette/websockets.py +193 -0
  73. prefect/blocks/core.py +3 -3
  74. prefect/blocks/notifications.py +8 -8
  75. prefect/client/base.py +1 -1
  76. prefect/client/cloud.py +1 -1
  77. prefect/client/orchestration.py +1 -1
  78. prefect/client/subscriptions.py +2 -6
  79. prefect/concurrency/services.py +1 -1
  80. prefect/context.py +3 -3
  81. prefect/deployments/deployments.py +3 -3
  82. prefect/engine.py +69 -9
  83. prefect/events/clients.py +1 -1
  84. prefect/filesystems.py +9 -9
  85. prefect/flow_runs.py +5 -1
  86. prefect/futures.py +1 -1
  87. prefect/infrastructure/container.py +3 -3
  88. prefect/infrastructure/kubernetes.py +4 -6
  89. prefect/infrastructure/process.py +3 -3
  90. prefect/input/run_input.py +1 -1
  91. prefect/logging/formatters.py +1 -1
  92. prefect/runner/server.py +3 -3
  93. prefect/settings.py +3 -4
  94. prefect/software/pip.py +1 -1
  95. prefect/task_engine.py +4 -0
  96. prefect/task_server.py +35 -17
  97. prefect/utilities/asyncutils.py +1 -1
  98. prefect/utilities/collections.py +1 -1
  99. {prefect_client-2.14.21.dist-info → prefect_client-2.15.0.dist-info}/METADATA +4 -2
  100. {prefect_client-2.14.21.dist-info → prefect_client-2.15.0.dist-info}/RECORD +103 -68
  101. {prefect_client-2.14.21.dist-info → prefect_client-2.15.0.dist-info}/LICENSE +0 -0
  102. {prefect_client-2.14.21.dist-info → prefect_client-2.15.0.dist-info}/WHEEL +0 -0
  103. {prefect_client-2.14.21.dist-info → prefect_client-2.15.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,28 @@
1
+ import hashlib
2
+
3
+ # Compat wrapper to always include the `usedforsecurity=...` parameter,
4
+ # which is only added from Python 3.9 onwards.
5
+ # We use this flag to indicate that we use `md5` hashes only for non-security
6
+ # cases (our ETag checksums).
7
+ # If we don't indicate that we're using MD5 for non-security related reasons,
8
+ # then attempting to use this function will raise an error when used
9
+ # environments which enable a strict "FIPs mode".
10
+ #
11
+ # See issue: https://github.com/encode/starlette/issues/1365
12
+ try:
13
+ # check if the Python version supports the parameter
14
+ # using usedforsecurity=False to avoid an exception on FIPS systems
15
+ # that reject usedforsecurity=True
16
+ hashlib.md5(b"data", usedforsecurity=False) # type: ignore[call-arg]
17
+
18
+ def md5_hexdigest(
19
+ data: bytes, *, usedforsecurity: bool = True
20
+ ) -> str: # pragma: no cover
21
+ return hashlib.md5( # type: ignore[call-arg]
22
+ data, usedforsecurity=usedforsecurity
23
+ ).hexdigest()
24
+
25
+ except TypeError: # pragma: no cover
26
+
27
+ def md5_hexdigest(data: bytes, *, usedforsecurity: bool = True) -> str:
28
+ return hashlib.md5(data).hexdigest()
@@ -0,0 +1,80 @@
1
+ import typing
2
+
3
+ from prefect._vendor.starlette._utils import is_async_callable
4
+ from prefect._vendor.starlette.concurrency import run_in_threadpool
5
+ from prefect._vendor.starlette.exceptions import HTTPException
6
+ from prefect._vendor.starlette.requests import Request
7
+ from prefect._vendor.starlette.types import (
8
+ ASGIApp,
9
+ ExceptionHandler,
10
+ Message,
11
+ Receive,
12
+ Scope,
13
+ Send,
14
+ )
15
+ from prefect._vendor.starlette.websockets import WebSocket
16
+
17
+ ExceptionHandlers = typing.Dict[typing.Any, ExceptionHandler]
18
+ StatusHandlers = typing.Dict[int, ExceptionHandler]
19
+
20
+
21
+ def _lookup_exception_handler(
22
+ exc_handlers: ExceptionHandlers, exc: Exception
23
+ ) -> typing.Optional[ExceptionHandler]:
24
+ for cls in type(exc).__mro__:
25
+ if cls in exc_handlers:
26
+ return exc_handlers[cls]
27
+ return None
28
+
29
+
30
+ def wrap_app_handling_exceptions(
31
+ app: ASGIApp, conn: typing.Union[Request, WebSocket]
32
+ ) -> ASGIApp:
33
+ exception_handlers: ExceptionHandlers
34
+ status_handlers: StatusHandlers
35
+ try:
36
+ exception_handlers, status_handlers = conn.scope["starlette.exception_handlers"]
37
+ except KeyError:
38
+ exception_handlers, status_handlers = {}, {}
39
+
40
+ async def wrapped_app(scope: Scope, receive: Receive, send: Send) -> None:
41
+ response_started = False
42
+
43
+ async def sender(message: Message) -> None:
44
+ nonlocal response_started
45
+
46
+ if message["type"] == "http.response.start":
47
+ response_started = True
48
+ await send(message)
49
+
50
+ try:
51
+ await app(scope, receive, sender)
52
+ except Exception as exc:
53
+ handler = None
54
+
55
+ if isinstance(exc, HTTPException):
56
+ handler = status_handlers.get(exc.status_code)
57
+
58
+ if handler is None:
59
+ handler = _lookup_exception_handler(exception_handlers, exc)
60
+
61
+ if handler is None:
62
+ raise exc
63
+
64
+ if response_started:
65
+ msg = "Caught handled exception, but response already started."
66
+ raise RuntimeError(msg) from exc
67
+
68
+ if scope["type"] == "http":
69
+ if is_async_callable(handler):
70
+ response = await handler(conn, exc)
71
+ else:
72
+ response = await run_in_threadpool(handler, conn, exc)
73
+ await response(scope, receive, sender)
74
+ elif scope["type"] == "websocket":
75
+ if is_async_callable(handler):
76
+ await handler(conn, exc)
77
+ else:
78
+ await run_in_threadpool(handler, conn, exc)
79
+
80
+ return wrapped_app
@@ -0,0 +1,88 @@
1
+ import asyncio
2
+ import functools
3
+ import sys
4
+ import typing
5
+ from contextlib import contextmanager
6
+
7
+ if sys.version_info >= (3, 10): # pragma: no cover
8
+ from typing import TypeGuard
9
+ else: # pragma: no cover
10
+ from typing_extensions import TypeGuard
11
+
12
+ has_exceptiongroups = True
13
+ if sys.version_info < (3, 11): # pragma: no cover
14
+ try:
15
+ from exceptiongroup import BaseExceptionGroup
16
+ except ImportError:
17
+ has_exceptiongroups = False
18
+
19
+ T = typing.TypeVar("T")
20
+ AwaitableCallable = typing.Callable[..., typing.Awaitable[T]]
21
+
22
+
23
+ @typing.overload
24
+ def is_async_callable(obj: AwaitableCallable[T]) -> TypeGuard[AwaitableCallable[T]]:
25
+ ...
26
+
27
+
28
+ @typing.overload
29
+ def is_async_callable(obj: typing.Any) -> TypeGuard[AwaitableCallable[typing.Any]]:
30
+ ...
31
+
32
+
33
+ def is_async_callable(obj: typing.Any) -> typing.Any:
34
+ while isinstance(obj, functools.partial):
35
+ obj = obj.func
36
+
37
+ return asyncio.iscoroutinefunction(obj) or (
38
+ callable(obj) and asyncio.iscoroutinefunction(obj.__call__)
39
+ )
40
+
41
+
42
+ T_co = typing.TypeVar("T_co", covariant=True)
43
+
44
+
45
+ class AwaitableOrContextManager(
46
+ typing.Awaitable[T_co], typing.AsyncContextManager[T_co], typing.Protocol[T_co]
47
+ ):
48
+ ...
49
+
50
+
51
+ class SupportsAsyncClose(typing.Protocol):
52
+ async def close(self) -> None:
53
+ ... # pragma: no cover
54
+
55
+
56
+ SupportsAsyncCloseType = typing.TypeVar(
57
+ "SupportsAsyncCloseType", bound=SupportsAsyncClose, covariant=False
58
+ )
59
+
60
+
61
+ class AwaitableOrContextManagerWrapper(typing.Generic[SupportsAsyncCloseType]):
62
+ __slots__ = ("aw", "entered")
63
+
64
+ def __init__(self, aw: typing.Awaitable[SupportsAsyncCloseType]) -> None:
65
+ self.aw = aw
66
+
67
+ def __await__(self) -> typing.Generator[typing.Any, None, SupportsAsyncCloseType]:
68
+ return self.aw.__await__()
69
+
70
+ async def __aenter__(self) -> SupportsAsyncCloseType:
71
+ self.entered = await self.aw
72
+ return self.entered
73
+
74
+ async def __aexit__(self, *args: typing.Any) -> typing.Union[None, bool]:
75
+ await self.entered.close()
76
+ return None
77
+
78
+
79
+ @contextmanager
80
+ def collapse_excgroups() -> typing.Generator[None, None, None]:
81
+ try:
82
+ yield
83
+ except BaseException as exc:
84
+ if has_exceptiongroups:
85
+ while isinstance(exc, BaseExceptionGroup) and len(exc.exceptions) == 1:
86
+ exc = exc.exceptions[0] # pragma: no cover
87
+
88
+ raise exc
@@ -0,0 +1,261 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ import warnings
5
+
6
+ from prefect._vendor.starlette.datastructures import State, URLPath
7
+ from prefect._vendor.starlette.middleware import Middleware
8
+ from prefect._vendor.starlette.middleware.base import BaseHTTPMiddleware
9
+ from prefect._vendor.starlette.middleware.errors import ServerErrorMiddleware
10
+ from prefect._vendor.starlette.middleware.exceptions import ExceptionMiddleware
11
+ from prefect._vendor.starlette.requests import Request
12
+ from prefect._vendor.starlette.responses import Response
13
+ from prefect._vendor.starlette.routing import BaseRoute, Router
14
+ from prefect._vendor.starlette.types import (
15
+ ASGIApp,
16
+ ExceptionHandler,
17
+ Lifespan,
18
+ Receive,
19
+ Scope,
20
+ Send,
21
+ )
22
+ from prefect._vendor.starlette.websockets import WebSocket
23
+
24
+ AppType = typing.TypeVar("AppType", bound="Starlette")
25
+
26
+
27
+ class Starlette:
28
+ """
29
+ Creates an application instance.
30
+
31
+ **Parameters:**
32
+
33
+ * **debug** - Boolean indicating if debug tracebacks should be returned on errors.
34
+ * **routes** - A list of routes to serve incoming HTTP and WebSocket requests.
35
+ * **middleware** - A list of middleware to run for every request. A starlette
36
+ application will always automatically include two middleware classes.
37
+ `ServerErrorMiddleware` is added as the very outermost middleware, to handle
38
+ any uncaught errors occurring anywhere in the entire stack.
39
+ `ExceptionMiddleware` is added as the very innermost middleware, to deal
40
+ with handled exception cases occurring in the routing or endpoints.
41
+ * **exception_handlers** - A mapping of either integer status codes,
42
+ or exception class types onto callables which handle the exceptions.
43
+ Exception handler callables should be of the form
44
+ `handler(request, exc) -> response` and may be either standard functions, or
45
+ async functions.
46
+ * **on_startup** - A list of callables to run on application startup.
47
+ Startup handler callables do not take any arguments, and may be either
48
+ standard functions, or async functions.
49
+ * **on_shutdown** - A list of callables to run on application shutdown.
50
+ Shutdown handler callables do not take any arguments, and may be either
51
+ standard functions, or async functions.
52
+ * **lifespan** - A lifespan context function, which can be used to perform
53
+ startup and shutdown tasks. This is a newer style that replaces the
54
+ `on_startup` and `on_shutdown` handlers. Use one or the other, not both.
55
+ """
56
+
57
+ def __init__(
58
+ self: "AppType",
59
+ debug: bool = False,
60
+ routes: typing.Sequence[BaseRoute] | None = None,
61
+ middleware: typing.Sequence[Middleware] | None = None,
62
+ exception_handlers: typing.Mapping[typing.Any, ExceptionHandler] | None = None,
63
+ on_startup: typing.Sequence[typing.Callable[[], typing.Any]] | None = None,
64
+ on_shutdown: typing.Sequence[typing.Callable[[], typing.Any]] | None = None,
65
+ lifespan: typing.Optional[Lifespan["AppType"]] = None,
66
+ ) -> None:
67
+ # The lifespan context function is a newer style that replaces
68
+ # on_startup / on_shutdown handlers. Use one or the other, not both.
69
+ assert lifespan is None or (
70
+ on_startup is None and on_shutdown is None
71
+ ), "Use either 'lifespan' or 'on_startup'/'on_shutdown', not both."
72
+
73
+ self.debug = debug
74
+ self.state = State()
75
+ self.router = Router(
76
+ routes, on_startup=on_startup, on_shutdown=on_shutdown, lifespan=lifespan
77
+ )
78
+ self.exception_handlers = (
79
+ {} if exception_handlers is None else dict(exception_handlers)
80
+ )
81
+ self.user_middleware = [] if middleware is None else list(middleware)
82
+ self.middleware_stack: typing.Optional[ASGIApp] = None
83
+
84
+ def build_middleware_stack(self) -> ASGIApp:
85
+ debug = self.debug
86
+ error_handler = None
87
+ exception_handlers: typing.Dict[
88
+ typing.Any, typing.Callable[[Request, Exception], Response]
89
+ ] = {}
90
+
91
+ for key, value in self.exception_handlers.items():
92
+ if key in (500, Exception):
93
+ error_handler = value
94
+ else:
95
+ exception_handlers[key] = value
96
+
97
+ middleware = (
98
+ [Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]
99
+ + self.user_middleware
100
+ + [
101
+ Middleware(
102
+ ExceptionMiddleware, handlers=exception_handlers, debug=debug
103
+ )
104
+ ]
105
+ )
106
+
107
+ app = self.router
108
+ for cls, options in reversed(middleware):
109
+ app = cls(app=app, **options)
110
+ return app
111
+
112
+ @property
113
+ def routes(self) -> typing.List[BaseRoute]:
114
+ return self.router.routes
115
+
116
+ def url_path_for(self, name: str, /, **path_params: typing.Any) -> URLPath:
117
+ return self.router.url_path_for(name, **path_params)
118
+
119
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
120
+ scope["app"] = self
121
+ if self.middleware_stack is None:
122
+ self.middleware_stack = self.build_middleware_stack()
123
+ await self.middleware_stack(scope, receive, send)
124
+
125
+ def on_event(self, event_type: str) -> typing.Callable: # type: ignore[type-arg]
126
+ return self.router.on_event(event_type) # pragma: nocover
127
+
128
+ def mount(self, path: str, app: ASGIApp, name: str | None = None) -> None:
129
+ self.router.mount(path, app=app, name=name) # pragma: no cover
130
+
131
+ def host(self, host: str, app: ASGIApp, name: str | None = None) -> None:
132
+ self.router.host(host, app=app, name=name) # pragma: no cover
133
+
134
+ def add_middleware(self, middleware_class: type, **options: typing.Any) -> None:
135
+ if self.middleware_stack is not None: # pragma: no cover
136
+ raise RuntimeError("Cannot add middleware after an application has started")
137
+ self.user_middleware.insert(0, Middleware(middleware_class, **options))
138
+
139
+ def add_exception_handler(
140
+ self,
141
+ exc_class_or_status_code: int | typing.Type[Exception],
142
+ handler: ExceptionHandler,
143
+ ) -> None: # pragma: no cover
144
+ self.exception_handlers[exc_class_or_status_code] = handler
145
+
146
+ def add_event_handler(
147
+ self,
148
+ event_type: str,
149
+ func: typing.Callable, # type: ignore[type-arg]
150
+ ) -> None: # pragma: no cover
151
+ self.router.add_event_handler(event_type, func)
152
+
153
+ def add_route(
154
+ self,
155
+ path: str,
156
+ route: typing.Callable[[Request], typing.Awaitable[Response] | Response],
157
+ methods: typing.Optional[typing.List[str]] = None,
158
+ name: typing.Optional[str] = None,
159
+ include_in_schema: bool = True,
160
+ ) -> None: # pragma: no cover
161
+ self.router.add_route(
162
+ path, route, methods=methods, name=name, include_in_schema=include_in_schema
163
+ )
164
+
165
+ def add_websocket_route(
166
+ self,
167
+ path: str,
168
+ route: typing.Callable[[WebSocket], typing.Awaitable[None]],
169
+ name: str | None = None,
170
+ ) -> None: # pragma: no cover
171
+ self.router.add_websocket_route(path, route, name=name)
172
+
173
+ def exception_handler(
174
+ self, exc_class_or_status_code: int | typing.Type[Exception]
175
+ ) -> typing.Callable: # type: ignore[type-arg]
176
+ warnings.warn(
177
+ "The `exception_handler` decorator is deprecated, and will be removed in version 1.0.0. " # noqa: E501
178
+ "Refer to https://www.starlette.io/exceptions/ for the recommended approach.", # noqa: E501
179
+ DeprecationWarning,
180
+ )
181
+
182
+ def decorator(func: typing.Callable) -> typing.Callable: # type: ignore[type-arg] # noqa: E501
183
+ self.add_exception_handler(exc_class_or_status_code, func)
184
+ return func
185
+
186
+ return decorator
187
+
188
+ def route(
189
+ self,
190
+ path: str,
191
+ methods: typing.List[str] | None = None,
192
+ name: str | None = None,
193
+ include_in_schema: bool = True,
194
+ ) -> typing.Callable: # type: ignore[type-arg]
195
+ """
196
+ We no longer document this decorator style API, and its usage is discouraged.
197
+ Instead you should use the following approach:
198
+
199
+ >>> routes = [Route(path, endpoint=...), ...]
200
+ >>> app = Starlette(routes=routes)
201
+ """
202
+ warnings.warn(
203
+ "The `route` decorator is deprecated, and will be removed in version 1.0.0. " # noqa: E501
204
+ "Refer to https://www.starlette.io/routing/ for the recommended approach.", # noqa: E501
205
+ DeprecationWarning,
206
+ )
207
+
208
+ def decorator(func: typing.Callable) -> typing.Callable: # type: ignore[type-arg] # noqa: E501
209
+ self.router.add_route(
210
+ path,
211
+ func,
212
+ methods=methods,
213
+ name=name,
214
+ include_in_schema=include_in_schema,
215
+ )
216
+ return func
217
+
218
+ return decorator
219
+
220
+ def websocket_route(self, path: str, name: str | None = None) -> typing.Callable: # type: ignore[type-arg]
221
+ """
222
+ We no longer document this decorator style API, and its usage is discouraged.
223
+ Instead you should use the following approach:
224
+
225
+ >>> routes = [WebSocketRoute(path, endpoint=...), ...]
226
+ >>> app = Starlette(routes=routes)
227
+ """
228
+ warnings.warn(
229
+ "The `websocket_route` decorator is deprecated, and will be removed in version 1.0.0. " # noqa: E501
230
+ "Refer to https://www.starlette.io/routing/#websocket-routing for the recommended approach.", # noqa: E501
231
+ DeprecationWarning,
232
+ )
233
+
234
+ def decorator(func: typing.Callable) -> typing.Callable: # type: ignore[type-arg] # noqa: E501
235
+ self.router.add_websocket_route(path, func, name=name)
236
+ return func
237
+
238
+ return decorator
239
+
240
+ def middleware(self, middleware_type: str) -> typing.Callable: # type: ignore[type-arg] # noqa: E501
241
+ """
242
+ We no longer document this decorator style API, and its usage is discouraged.
243
+ Instead you should use the following approach:
244
+
245
+ >>> middleware = [Middleware(...), ...]
246
+ >>> app = Starlette(middleware=middleware)
247
+ """
248
+ warnings.warn(
249
+ "The `middleware` decorator is deprecated, and will be removed in version 1.0.0. " # noqa: E501
250
+ "Refer to https://www.starlette.io/middleware/#using-middleware for recommended approach.", # noqa: E501
251
+ DeprecationWarning,
252
+ )
253
+ assert (
254
+ middleware_type == "http"
255
+ ), 'Currently only middleware("http") is supported.'
256
+
257
+ def decorator(func: typing.Callable) -> typing.Callable: # type: ignore[type-arg] # noqa: E501
258
+ self.add_middleware(BaseHTTPMiddleware, dispatch=func)
259
+ return func
260
+
261
+ return decorator
@@ -0,0 +1,159 @@
1
+ import functools
2
+ import inspect
3
+ import sys
4
+ import typing
5
+ from urllib.parse import urlencode
6
+
7
+ if sys.version_info >= (3, 10): # pragma: no cover
8
+ from typing import ParamSpec
9
+ else: # pragma: no cover
10
+ from typing_extensions import ParamSpec
11
+
12
+ from prefect._vendor.starlette._utils import is_async_callable
13
+ from prefect._vendor.starlette.exceptions import HTTPException
14
+ from prefect._vendor.starlette.requests import HTTPConnection, Request
15
+ from prefect._vendor.starlette.responses import RedirectResponse
16
+ from prefect._vendor.starlette.websockets import WebSocket
17
+
18
+ _P = ParamSpec("_P")
19
+
20
+
21
+ def has_required_scope(conn: HTTPConnection, scopes: typing.Sequence[str]) -> bool:
22
+ for scope in scopes:
23
+ if scope not in conn.auth.scopes:
24
+ return False
25
+ return True
26
+
27
+
28
+ def requires(
29
+ scopes: typing.Union[str, typing.Sequence[str]],
30
+ status_code: int = 403,
31
+ redirect: typing.Optional[str] = None,
32
+ ) -> typing.Callable[
33
+ [typing.Callable[_P, typing.Any]], typing.Callable[_P, typing.Any]
34
+ ]:
35
+ scopes_list = [scopes] if isinstance(scopes, str) else list(scopes)
36
+
37
+ def decorator(
38
+ func: typing.Callable[_P, typing.Any],
39
+ ) -> typing.Callable[_P, typing.Any]:
40
+ sig = inspect.signature(func)
41
+ for idx, parameter in enumerate(sig.parameters.values()):
42
+ if parameter.name == "request" or parameter.name == "websocket":
43
+ type_ = parameter.name
44
+ break
45
+ else:
46
+ raise Exception(
47
+ f'No "request" or "websocket" argument on function "{func}"'
48
+ )
49
+
50
+ if type_ == "websocket":
51
+ # Handle websocket functions. (Always async)
52
+ @functools.wraps(func)
53
+ async def websocket_wrapper(*args: _P.args, **kwargs: _P.kwargs) -> None:
54
+ websocket = kwargs.get(
55
+ "websocket", args[idx] if idx < len(args) else None
56
+ )
57
+ assert isinstance(websocket, WebSocket)
58
+
59
+ if not has_required_scope(websocket, scopes_list):
60
+ await websocket.close()
61
+ else:
62
+ await func(*args, **kwargs)
63
+
64
+ return websocket_wrapper
65
+
66
+ elif is_async_callable(func):
67
+ # Handle async request/response functions.
68
+ @functools.wraps(func)
69
+ async def async_wrapper(*args: _P.args, **kwargs: _P.kwargs) -> typing.Any:
70
+ request = kwargs.get("request", args[idx] if idx < len(args) else None)
71
+ assert isinstance(request, Request)
72
+
73
+ if not has_required_scope(request, scopes_list):
74
+ if redirect is not None:
75
+ orig_request_qparam = urlencode({"next": str(request.url)})
76
+ next_url = "{redirect_path}?{orig_request}".format(
77
+ redirect_path=request.url_for(redirect),
78
+ orig_request=orig_request_qparam,
79
+ )
80
+ return RedirectResponse(url=next_url, status_code=303)
81
+ raise HTTPException(status_code=status_code)
82
+ return await func(*args, **kwargs)
83
+
84
+ return async_wrapper
85
+
86
+ else:
87
+ # Handle sync request/response functions.
88
+ @functools.wraps(func)
89
+ def sync_wrapper(*args: _P.args, **kwargs: _P.kwargs) -> typing.Any:
90
+ request = kwargs.get("request", args[idx] if idx < len(args) else None)
91
+ assert isinstance(request, Request)
92
+
93
+ if not has_required_scope(request, scopes_list):
94
+ if redirect is not None:
95
+ orig_request_qparam = urlencode({"next": str(request.url)})
96
+ next_url = "{redirect_path}?{orig_request}".format(
97
+ redirect_path=request.url_for(redirect),
98
+ orig_request=orig_request_qparam,
99
+ )
100
+ return RedirectResponse(url=next_url, status_code=303)
101
+ raise HTTPException(status_code=status_code)
102
+ return func(*args, **kwargs)
103
+
104
+ return sync_wrapper
105
+
106
+ return decorator
107
+
108
+
109
+ class AuthenticationError(Exception):
110
+ pass
111
+
112
+
113
+ class AuthenticationBackend:
114
+ async def authenticate(
115
+ self, conn: HTTPConnection
116
+ ) -> typing.Optional[typing.Tuple["AuthCredentials", "BaseUser"]]:
117
+ raise NotImplementedError() # pragma: no cover
118
+
119
+
120
+ class AuthCredentials:
121
+ def __init__(self, scopes: typing.Optional[typing.Sequence[str]] = None):
122
+ self.scopes = [] if scopes is None else list(scopes)
123
+
124
+
125
+ class BaseUser:
126
+ @property
127
+ def is_authenticated(self) -> bool:
128
+ raise NotImplementedError() # pragma: no cover
129
+
130
+ @property
131
+ def display_name(self) -> str:
132
+ raise NotImplementedError() # pragma: no cover
133
+
134
+ @property
135
+ def identity(self) -> str:
136
+ raise NotImplementedError() # pragma: no cover
137
+
138
+
139
+ class SimpleUser(BaseUser):
140
+ def __init__(self, username: str) -> None:
141
+ self.username = username
142
+
143
+ @property
144
+ def is_authenticated(self) -> bool:
145
+ return True
146
+
147
+ @property
148
+ def display_name(self) -> str:
149
+ return self.username
150
+
151
+
152
+ class UnauthenticatedUser(BaseUser):
153
+ @property
154
+ def is_authenticated(self) -> bool:
155
+ return False
156
+
157
+ @property
158
+ def display_name(self) -> str:
159
+ return ""
@@ -0,0 +1,43 @@
1
+ import sys
2
+ import typing
3
+
4
+ if sys.version_info >= (3, 10): # pragma: no cover
5
+ from typing import ParamSpec
6
+ else: # pragma: no cover
7
+ from typing_extensions import ParamSpec
8
+
9
+ from prefect._vendor.starlette._utils import is_async_callable
10
+ from prefect._vendor.starlette.concurrency import run_in_threadpool
11
+
12
+ P = ParamSpec("P")
13
+
14
+
15
+ class BackgroundTask:
16
+ def __init__(
17
+ self, func: typing.Callable[P, typing.Any], *args: P.args, **kwargs: P.kwargs
18
+ ) -> None:
19
+ self.func = func
20
+ self.args = args
21
+ self.kwargs = kwargs
22
+ self.is_async = is_async_callable(func)
23
+
24
+ async def __call__(self) -> None:
25
+ if self.is_async:
26
+ await self.func(*self.args, **self.kwargs)
27
+ else:
28
+ await run_in_threadpool(self.func, *self.args, **self.kwargs)
29
+
30
+
31
+ class BackgroundTasks(BackgroundTask):
32
+ def __init__(self, tasks: typing.Optional[typing.Sequence[BackgroundTask]] = None):
33
+ self.tasks = list(tasks) if tasks else []
34
+
35
+ def add_task(
36
+ self, func: typing.Callable[P, typing.Any], *args: P.args, **kwargs: P.kwargs
37
+ ) -> None:
38
+ task = BackgroundTask(func, *args, **kwargs)
39
+ self.tasks.append(task)
40
+
41
+ async def __call__(self) -> None:
42
+ for task in self.tasks:
43
+ await task()