prefect-client 2.14.20__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.
- prefect/_internal/concurrency/api.py +37 -2
- prefect/_internal/concurrency/calls.py +9 -0
- prefect/_internal/concurrency/cancellation.py +3 -1
- prefect/_internal/concurrency/event_loop.py +2 -2
- prefect/_internal/concurrency/threads.py +3 -2
- prefect/_internal/pydantic/annotations/pendulum.py +4 -4
- prefect/_internal/pydantic/v2_schema.py +2 -2
- prefect/_vendor/fastapi/__init__.py +1 -1
- prefect/_vendor/fastapi/applications.py +13 -13
- prefect/_vendor/fastapi/background.py +3 -1
- prefect/_vendor/fastapi/concurrency.py +7 -3
- prefect/_vendor/fastapi/datastructures.py +9 -7
- prefect/_vendor/fastapi/dependencies/utils.py +12 -7
- prefect/_vendor/fastapi/encoders.py +1 -1
- prefect/_vendor/fastapi/exception_handlers.py +7 -4
- prefect/_vendor/fastapi/exceptions.py +4 -2
- prefect/_vendor/fastapi/middleware/__init__.py +1 -1
- prefect/_vendor/fastapi/middleware/asyncexitstack.py +1 -1
- prefect/_vendor/fastapi/middleware/cors.py +3 -1
- prefect/_vendor/fastapi/middleware/gzip.py +3 -1
- prefect/_vendor/fastapi/middleware/httpsredirect.py +1 -1
- prefect/_vendor/fastapi/middleware/trustedhost.py +1 -1
- prefect/_vendor/fastapi/middleware/wsgi.py +3 -1
- prefect/_vendor/fastapi/openapi/docs.py +1 -1
- prefect/_vendor/fastapi/openapi/utils.py +3 -3
- prefect/_vendor/fastapi/requests.py +4 -2
- prefect/_vendor/fastapi/responses.py +13 -7
- prefect/_vendor/fastapi/routing.py +15 -15
- prefect/_vendor/fastapi/security/api_key.py +3 -3
- prefect/_vendor/fastapi/security/http.py +2 -2
- prefect/_vendor/fastapi/security/oauth2.py +2 -2
- prefect/_vendor/fastapi/security/open_id_connect_url.py +3 -3
- prefect/_vendor/fastapi/staticfiles.py +1 -1
- prefect/_vendor/fastapi/templating.py +3 -1
- prefect/_vendor/fastapi/testclient.py +1 -1
- prefect/_vendor/fastapi/utils.py +3 -3
- prefect/_vendor/fastapi/websockets.py +7 -3
- prefect/_vendor/starlette/__init__.py +1 -0
- prefect/_vendor/starlette/_compat.py +28 -0
- prefect/_vendor/starlette/_exception_handler.py +80 -0
- prefect/_vendor/starlette/_utils.py +88 -0
- prefect/_vendor/starlette/applications.py +261 -0
- prefect/_vendor/starlette/authentication.py +159 -0
- prefect/_vendor/starlette/background.py +43 -0
- prefect/_vendor/starlette/concurrency.py +59 -0
- prefect/_vendor/starlette/config.py +151 -0
- prefect/_vendor/starlette/convertors.py +87 -0
- prefect/_vendor/starlette/datastructures.py +707 -0
- prefect/_vendor/starlette/endpoints.py +130 -0
- prefect/_vendor/starlette/exceptions.py +60 -0
- prefect/_vendor/starlette/formparsers.py +276 -0
- prefect/_vendor/starlette/middleware/__init__.py +17 -0
- prefect/_vendor/starlette/middleware/authentication.py +52 -0
- prefect/_vendor/starlette/middleware/base.py +220 -0
- prefect/_vendor/starlette/middleware/cors.py +176 -0
- prefect/_vendor/starlette/middleware/errors.py +265 -0
- prefect/_vendor/starlette/middleware/exceptions.py +74 -0
- prefect/_vendor/starlette/middleware/gzip.py +113 -0
- prefect/_vendor/starlette/middleware/httpsredirect.py +19 -0
- prefect/_vendor/starlette/middleware/sessions.py +82 -0
- prefect/_vendor/starlette/middleware/trustedhost.py +64 -0
- prefect/_vendor/starlette/middleware/wsgi.py +147 -0
- prefect/_vendor/starlette/requests.py +328 -0
- prefect/_vendor/starlette/responses.py +347 -0
- prefect/_vendor/starlette/routing.py +933 -0
- prefect/_vendor/starlette/schemas.py +154 -0
- prefect/_vendor/starlette/staticfiles.py +248 -0
- prefect/_vendor/starlette/status.py +199 -0
- prefect/_vendor/starlette/templating.py +231 -0
- prefect/_vendor/starlette/testclient.py +805 -0
- prefect/_vendor/starlette/types.py +30 -0
- prefect/_vendor/starlette/websockets.py +193 -0
- prefect/blocks/core.py +3 -3
- prefect/blocks/notifications.py +10 -9
- prefect/client/base.py +1 -1
- prefect/client/cloud.py +1 -1
- prefect/client/orchestration.py +1 -1
- prefect/client/schemas/objects.py +11 -0
- prefect/client/subscriptions.py +19 -12
- prefect/concurrency/services.py +1 -1
- prefect/context.py +4 -4
- prefect/deployments/deployments.py +3 -3
- prefect/engine.py +89 -17
- prefect/events/clients.py +1 -1
- prefect/events/utilities.py +4 -1
- prefect/events/worker.py +10 -6
- prefect/filesystems.py +9 -9
- prefect/flow_runs.py +5 -1
- prefect/futures.py +1 -1
- prefect/infrastructure/container.py +3 -3
- prefect/infrastructure/kubernetes.py +4 -6
- prefect/infrastructure/process.py +3 -3
- prefect/input/run_input.py +1 -1
- prefect/logging/formatters.py +1 -1
- prefect/results.py +3 -6
- prefect/runner/server.py +4 -4
- prefect/settings.py +23 -3
- prefect/software/pip.py +1 -1
- prefect/task_engine.py +14 -11
- prefect/task_server.py +69 -35
- prefect/utilities/asyncutils.py +12 -2
- prefect/utilities/collections.py +1 -1
- prefect/utilities/filesystem.py +10 -5
- {prefect_client-2.14.20.dist-info → prefect_client-2.15.0.dist-info}/METADATA +4 -2
- {prefect_client-2.14.20.dist-info → prefect_client-2.15.0.dist-info}/RECORD +108 -73
- {prefect_client-2.14.20.dist-info → prefect_client-2.15.0.dist-info}/LICENSE +0 -0
- {prefect_client-2.14.20.dist-info → prefect_client-2.15.0.dist-info}/WHEEL +0 -0
- {prefect_client-2.14.20.dist-info → prefect_client-2.15.0.dist-info}/top_level.txt +0 -0
prefect/task_engine.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import threading
|
1
2
|
from contextlib import AsyncExitStack
|
2
3
|
from typing import (
|
3
4
|
Any,
|
@@ -8,7 +9,7 @@ from typing import (
|
|
8
9
|
)
|
9
10
|
|
10
11
|
import anyio
|
11
|
-
|
12
|
+
import greenback
|
12
13
|
from typing_extensions import Literal
|
13
14
|
|
14
15
|
from prefect._internal.concurrency.api import create_call, from_async, from_sync
|
@@ -21,7 +22,7 @@ from prefect.engine import (
|
|
21
22
|
)
|
22
23
|
from prefect.futures import PrefectFuture
|
23
24
|
from prefect.results import ResultFactory
|
24
|
-
from prefect.task_runners import BaseTaskRunner
|
25
|
+
from prefect.task_runners import BaseTaskRunner
|
25
26
|
from prefect.tasks import Task
|
26
27
|
from prefect.utilities.asyncutils import sync_compatible
|
27
28
|
|
@@ -32,28 +33,28 @@ EngineReturnType = Literal["future", "state", "result"]
|
|
32
33
|
async def submit_autonomous_task_to_engine(
|
33
34
|
task: Task,
|
34
35
|
task_run: TaskRun,
|
36
|
+
task_runner: Type[BaseTaskRunner],
|
35
37
|
parameters: Optional[Dict] = None,
|
36
38
|
wait_for: Optional[Iterable[PrefectFuture]] = None,
|
37
39
|
mapped: bool = False,
|
38
40
|
return_type: EngineReturnType = "future",
|
39
|
-
|
41
|
+
client=None,
|
40
42
|
) -> Any:
|
41
|
-
parameters = parameters or {}
|
42
43
|
async with AsyncExitStack() as stack:
|
44
|
+
if not task_runner._started:
|
45
|
+
task_runner_ctx = await stack.enter_async_context(task_runner.start())
|
46
|
+
else:
|
47
|
+
task_runner_ctx = task_runner
|
48
|
+
parameters = parameters or {}
|
43
49
|
with EngineContext(
|
44
50
|
flow=None,
|
45
51
|
flow_run=None,
|
46
52
|
autonomous_task_run=task_run,
|
47
|
-
task_runner=
|
48
|
-
|
49
|
-
),
|
50
|
-
client=await stack.enter_async_context(get_client()),
|
53
|
+
task_runner=task_runner_ctx,
|
54
|
+
client=client or await stack.enter_async_context(get_client()),
|
51
55
|
parameters=parameters,
|
52
56
|
result_factory=await ResultFactory.from_task(task),
|
53
57
|
background_tasks=await stack.enter_async_context(anyio.create_task_group()),
|
54
|
-
sync_portal=(
|
55
|
-
stack.enter_context(start_blocking_portal()) if task.isasync else None
|
56
|
-
),
|
57
58
|
) as flow_run_context:
|
58
59
|
begin_run = create_call(
|
59
60
|
begin_task_map if mapped else get_task_call_return_value,
|
@@ -63,8 +64,10 @@ async def submit_autonomous_task_to_engine(
|
|
63
64
|
wait_for=wait_for,
|
64
65
|
return_type=return_type,
|
65
66
|
task_runner=task_runner,
|
67
|
+
user_thread=threading.current_thread(),
|
66
68
|
)
|
67
69
|
if task.isasync:
|
68
70
|
return await from_async.wait_for_call_in_loop_thread(begin_run)
|
69
71
|
else:
|
72
|
+
await greenback.ensure_portal()
|
70
73
|
return from_sync.wait_for_call_in_loop_thread(begin_run)
|
prefect/task_server.py
CHANGED
@@ -1,58 +1,67 @@
|
|
1
1
|
import asyncio
|
2
2
|
import signal
|
3
3
|
import sys
|
4
|
+
from contextlib import AsyncExitStack
|
4
5
|
from functools import partial
|
5
|
-
from typing import
|
6
|
+
from typing import Optional, Type
|
6
7
|
|
7
8
|
import anyio
|
8
9
|
import anyio.abc
|
9
|
-
import pendulum
|
10
10
|
|
11
11
|
from prefect import Task, get_client
|
12
12
|
from prefect._internal.concurrency.api import create_call, from_sync
|
13
13
|
from prefect.client.schemas.objects import TaskRun
|
14
14
|
from prefect.client.subscriptions import Subscription
|
15
|
+
from prefect.engine import propose_state
|
15
16
|
from prefect.logging.loggers import get_logger
|
16
17
|
from prefect.results import ResultFactory
|
17
18
|
from prefect.settings import (
|
18
19
|
PREFECT_EXPERIMENTAL_ENABLE_TASK_SCHEDULING,
|
19
20
|
PREFECT_TASK_SCHEDULING_DELETE_FAILED_SUBMISSIONS,
|
20
21
|
)
|
22
|
+
from prefect.states import Pending
|
21
23
|
from prefect.task_engine import submit_autonomous_task_to_engine
|
24
|
+
from prefect.task_runners import BaseTaskRunner, ConcurrentTaskRunner
|
22
25
|
from prefect.utilities.asyncutils import asyncnullcontext, sync_compatible
|
23
|
-
from prefect.utilities.collections import distinct
|
24
26
|
from prefect.utilities.processutils import _register_signal
|
25
27
|
|
26
28
|
logger = get_logger("task_server")
|
27
29
|
|
28
30
|
|
31
|
+
class StopTaskServer(Exception):
|
32
|
+
"""Raised when the task server is stopped."""
|
33
|
+
|
34
|
+
pass
|
35
|
+
|
36
|
+
|
29
37
|
class TaskServer:
|
30
|
-
"""This class is responsible for serving tasks that may be executed autonomously
|
31
|
-
|
38
|
+
"""This class is responsible for serving tasks that may be executed autonomously by a
|
39
|
+
task runner in the engine.
|
32
40
|
|
33
|
-
When `start()` is called, the task server will
|
34
|
-
|
35
|
-
|
36
|
-
|
41
|
+
When `start()` is called, the task server will open a websocket connection to a
|
42
|
+
server-side queue of scheduled task runs. When a scheduled task run is found, the
|
43
|
+
scheduled task run is submitted to the engine for execution with a minimal `EngineContext`
|
44
|
+
so that the task run can be governed by orchestration rules.
|
37
45
|
|
38
46
|
Args:
|
39
47
|
- tasks: A list of tasks to serve. These tasks will be submitted to the engine
|
40
48
|
when a scheduled task run is found.
|
41
|
-
-
|
49
|
+
- task_runner: The task runner to use for executing the tasks. Defaults to
|
50
|
+
`ConcurrentTaskRunner`.
|
42
51
|
"""
|
43
52
|
|
44
53
|
def __init__(
|
45
54
|
self,
|
46
55
|
*tasks: Task,
|
47
|
-
|
56
|
+
task_runner: Optional[Type[BaseTaskRunner]] = None,
|
48
57
|
):
|
49
58
|
self.tasks: list[Task] = tasks
|
50
|
-
self.
|
51
|
-
self.
|
52
|
-
self.
|
53
|
-
self.stopping = False
|
59
|
+
self.task_runner: Type[BaseTaskRunner] = task_runner or ConcurrentTaskRunner()
|
60
|
+
self.started: bool = False
|
61
|
+
self.stopping: bool = False
|
54
62
|
|
55
63
|
self._client = get_client()
|
64
|
+
self._exit_stack = AsyncExitStack()
|
56
65
|
|
57
66
|
if not asyncio.get_event_loop().is_running():
|
58
67
|
raise RuntimeError(
|
@@ -89,18 +98,21 @@ class TaskServer:
|
|
89
98
|
" calling .start()"
|
90
99
|
)
|
91
100
|
|
92
|
-
logger.info("Stopping task server...")
|
93
101
|
self.started = False
|
94
102
|
self.stopping = True
|
95
103
|
|
104
|
+
raise StopTaskServer
|
105
|
+
|
96
106
|
async def _subscribe_to_task_scheduling(self):
|
97
|
-
|
98
|
-
|
99
|
-
|
107
|
+
async for task_run in Subscription(
|
108
|
+
TaskRun,
|
109
|
+
"/task_runs/subscriptions/scheduled",
|
110
|
+
[task.task_key for task in self.tasks],
|
111
|
+
):
|
100
112
|
logger.info(f"Received task run: {task_run.id} - {task_run.name}")
|
101
|
-
await self.
|
113
|
+
await self._submit_scheduled_task_run(task_run)
|
102
114
|
|
103
|
-
async def
|
115
|
+
async def _submit_scheduled_task_run(self, task_run: TaskRun):
|
104
116
|
logger.debug(
|
105
117
|
f"Found task run: {task_run.name!r} in state: {task_run.state.name!r}"
|
106
118
|
)
|
@@ -142,7 +154,18 @@ class TaskServer:
|
|
142
154
|
f"Submitting run {task_run.name!r} of task {task.name!r} to engine"
|
143
155
|
)
|
144
156
|
|
145
|
-
|
157
|
+
state = await propose_state(
|
158
|
+
client=get_client(), # TODO prove that we cannot use self._client here
|
159
|
+
state=Pending(),
|
160
|
+
task_run_id=task_run.id,
|
161
|
+
)
|
162
|
+
|
163
|
+
if not state.is_pending():
|
164
|
+
logger.warning(
|
165
|
+
f"Aborted task run {task_run.id!r} -"
|
166
|
+
f" server returned a non-pending state {state.type.value!r}."
|
167
|
+
" Task run may have already begun execution."
|
168
|
+
)
|
146
169
|
|
147
170
|
self._runs_task_group.start_soon(
|
148
171
|
partial(
|
@@ -150,14 +173,17 @@ class TaskServer:
|
|
150
173
|
task=task,
|
151
174
|
task_run=task_run,
|
152
175
|
parameters=parameters,
|
176
|
+
task_runner=self.task_runner,
|
177
|
+
client=self._client,
|
153
178
|
)
|
154
179
|
)
|
155
180
|
|
156
181
|
async def __aenter__(self):
|
157
182
|
logger.debug("Starting task server...")
|
158
|
-
|
159
|
-
await self.
|
160
|
-
await self.
|
183
|
+
|
184
|
+
self._client = await self._exit_stack.enter_async_context(get_client())
|
185
|
+
await self._exit_stack.enter_async_context(self._runs_task_group)
|
186
|
+
await self._exit_stack.enter_async_context(self.task_runner.start())
|
161
187
|
|
162
188
|
self.started = True
|
163
189
|
return self
|
@@ -165,20 +191,21 @@ class TaskServer:
|
|
165
191
|
async def __aexit__(self, *exc_info):
|
166
192
|
logger.debug("Stopping task server...")
|
167
193
|
self.started = False
|
168
|
-
|
169
|
-
|
170
|
-
if self._client:
|
171
|
-
await self._client.__aexit__(*exc_info)
|
194
|
+
|
195
|
+
await self._exit_stack.__aexit__(*exc_info)
|
172
196
|
|
173
197
|
|
174
198
|
@sync_compatible
|
175
|
-
async def serve(*tasks: Task,
|
176
|
-
"""Serve the provided tasks so that
|
199
|
+
async def serve(*tasks: Task, task_runner: Optional[Type[BaseTaskRunner]] = None):
|
200
|
+
"""Serve the provided tasks so that their runs may be submitted to and executed.
|
201
|
+
in the engine. Tasks do not need to be within a flow run context to be submitted.
|
202
|
+
You must `.submit` the same task object that you pass to `serve`.
|
177
203
|
|
178
204
|
Args:
|
179
205
|
- tasks: A list of tasks to serve. When a scheduled task run is found for a
|
180
206
|
given task, the task run will be submitted to the engine for execution.
|
181
|
-
-
|
207
|
+
- task_runner: The task runner to use for executing the tasks. Defaults to
|
208
|
+
`ConcurrentTaskRunner`.
|
182
209
|
|
183
210
|
Example:
|
184
211
|
```python
|
@@ -193,7 +220,7 @@ async def serve(*tasks: Task, tags: Optional[Iterable[str]] = None):
|
|
193
220
|
def yell(message: str):
|
194
221
|
print(message.upper())
|
195
222
|
|
196
|
-
# starts a long-lived process that listens scheduled runs of these tasks
|
223
|
+
# starts a long-lived process that listens for scheduled runs of these tasks
|
197
224
|
if __name__ == "__main__":
|
198
225
|
serve(say, yell)
|
199
226
|
```
|
@@ -204,5 +231,12 @@ async def serve(*tasks: Task, tags: Optional[Iterable[str]] = None):
|
|
204
231
|
" to True."
|
205
232
|
)
|
206
233
|
|
207
|
-
task_server = TaskServer(*tasks,
|
208
|
-
|
234
|
+
task_server = TaskServer(*tasks, task_runner=task_runner)
|
235
|
+
try:
|
236
|
+
await task_server.start()
|
237
|
+
|
238
|
+
except StopTaskServer:
|
239
|
+
logger.info("Task server stopped.")
|
240
|
+
|
241
|
+
except asyncio.CancelledError:
|
242
|
+
logger.info("Task server interrupted, stopping...")
|
prefect/utilities/asyncutils.py
CHANGED
@@ -28,6 +28,8 @@ import anyio.abc
|
|
28
28
|
import sniffio
|
29
29
|
from typing_extensions import Literal, ParamSpec, TypeGuard
|
30
30
|
|
31
|
+
from prefect.logging import get_logger
|
32
|
+
|
31
33
|
T = TypeVar("T")
|
32
34
|
P = ParamSpec("P")
|
33
35
|
R = TypeVar("R")
|
@@ -40,6 +42,8 @@ EVENT_LOOP_GC_REFS = {}
|
|
40
42
|
|
41
43
|
PREFECT_THREAD_LIMITER: Optional[anyio.CapacityLimiter] = None
|
42
44
|
|
45
|
+
logger = get_logger()
|
46
|
+
|
43
47
|
|
44
48
|
def get_thread_limiter():
|
45
49
|
global PREFECT_THREAD_LIMITER
|
@@ -51,7 +55,7 @@ def get_thread_limiter():
|
|
51
55
|
|
52
56
|
|
53
57
|
def is_async_fn(
|
54
|
-
func: Union[Callable[P, R], Callable[P, Awaitable[R]]]
|
58
|
+
func: Union[Callable[P, R], Callable[P, Awaitable[R]]],
|
55
59
|
) -> TypeGuard[Callable[P, Awaitable[R]]]:
|
56
60
|
"""
|
57
61
|
Returns `True` if a function returns a coroutine.
|
@@ -334,7 +338,13 @@ async def add_event_loop_shutdown_callback(coroutine_fn: Callable[[], Awaitable]
|
|
334
338
|
EVENT_LOOP_GC_REFS[key] = on_shutdown(key)
|
335
339
|
|
336
340
|
# Begin iterating so it will be cleaned up as an incomplete generator
|
337
|
-
|
341
|
+
try:
|
342
|
+
await EVENT_LOOP_GC_REFS[key].__anext__()
|
343
|
+
# There is a poorly understood edge case we've seen in CI where the key is
|
344
|
+
# removed from the dict before we begin generator iteration.
|
345
|
+
except KeyError:
|
346
|
+
logger.warn("The event loop shutdown callback was not properly registered. ")
|
347
|
+
pass
|
338
348
|
|
339
349
|
|
340
350
|
class GatherIncomplete(RuntimeError):
|
prefect/utilities/collections.py
CHANGED
prefect/utilities/filesystem.py
CHANGED
@@ -3,6 +3,7 @@ Utilities for working with file systems
|
|
3
3
|
"""
|
4
4
|
import os
|
5
5
|
import pathlib
|
6
|
+
import threading
|
6
7
|
from contextlib import contextmanager
|
7
8
|
from pathlib import Path, PureWindowsPath
|
8
9
|
from typing import Union
|
@@ -51,6 +52,9 @@ def filter_files(
|
|
51
52
|
return included_files
|
52
53
|
|
53
54
|
|
55
|
+
chdir_lock = threading.Lock()
|
56
|
+
|
57
|
+
|
54
58
|
@contextmanager
|
55
59
|
def tmpchdir(path: str):
|
56
60
|
"""
|
@@ -62,11 +66,12 @@ def tmpchdir(path: str):
|
|
62
66
|
|
63
67
|
owd = os.getcwd()
|
64
68
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
69
|
+
with chdir_lock:
|
70
|
+
try:
|
71
|
+
os.chdir(path)
|
72
|
+
yield path
|
73
|
+
finally:
|
74
|
+
os.chdir(owd)
|
70
75
|
|
71
76
|
|
72
77
|
def filename(path: str) -> str:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: prefect-client
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.15.0
|
4
4
|
Summary: Workflow orchestration and management.
|
5
5
|
Home-page: https://www.prefect.io
|
6
6
|
Author: Prefect Technologies, Inc.
|
@@ -31,6 +31,7 @@ Requires-Dist: coolname <3.0.0,>=1.0.4
|
|
31
31
|
Requires-Dist: croniter <3.0.0,>=1.0.12
|
32
32
|
Requires-Dist: fsspec >=2022.5.0
|
33
33
|
Requires-Dist: graphviz >=0.20.1
|
34
|
+
Requires-Dist: greenback >=1.2.0
|
34
35
|
Requires-Dist: griffe >=0.20.0
|
35
36
|
Requires-Dist: httpcore <2.0.0,>=0.15.0
|
36
37
|
Requires-Dist: httpx[http2] !=0.23.2,>=0.23
|
@@ -46,12 +47,13 @@ Requires-Dist: pyyaml <7.0.0,>=5.4.1
|
|
46
47
|
Requires-Dist: rich <14.0,>=11.0
|
47
48
|
Requires-Dist: ruamel.yaml >=0.17.0
|
48
49
|
Requires-Dist: sniffio <2.0.0,>=1.3.0
|
49
|
-
Requires-Dist: starlette <0.33.0,>=0.27.0
|
50
50
|
Requires-Dist: toml >=0.10.0
|
51
51
|
Requires-Dist: typing-extensions <5.0.0,>=4.5.0
|
52
52
|
Requires-Dist: ujson <6.0.0,>=5.8.0
|
53
53
|
Requires-Dist: uvicorn >=0.14.0
|
54
54
|
Requires-Dist: websockets <13.0,>=10.4
|
55
|
+
Requires-Dist: itsdangerous
|
56
|
+
Requires-Dist: python-multipart >=0.0.7
|
55
57
|
Requires-Dist: importlib-metadata >=4.4 ; python_version < "3.10"
|
56
58
|
Requires-Dist: pendulum <3.0 ; python_version < "3.12"
|
57
59
|
Requires-Dist: pendulum <4,>=3.0.0 ; python_version >= "3.12"
|