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
@@ -0,0 +1,59 @@
|
|
1
|
+
import functools
|
2
|
+
import typing
|
3
|
+
import warnings
|
4
|
+
|
5
|
+
import anyio.to_thread
|
6
|
+
|
7
|
+
T = typing.TypeVar("T")
|
8
|
+
|
9
|
+
|
10
|
+
async def run_until_first_complete(*args: typing.Tuple[typing.Callable, dict]) -> None: # type: ignore[type-arg] # noqa: E501
|
11
|
+
warnings.warn(
|
12
|
+
"run_until_first_complete is deprecated "
|
13
|
+
"and will be removed in a future version.",
|
14
|
+
DeprecationWarning,
|
15
|
+
)
|
16
|
+
|
17
|
+
async with anyio.create_task_group() as task_group:
|
18
|
+
|
19
|
+
async def run(func: typing.Callable[[], typing.Coroutine]) -> None: # type: ignore[type-arg] # noqa: E501
|
20
|
+
await func()
|
21
|
+
task_group.cancel_scope.cancel()
|
22
|
+
|
23
|
+
for func, kwargs in args:
|
24
|
+
task_group.start_soon(run, functools.partial(func, **kwargs))
|
25
|
+
|
26
|
+
|
27
|
+
# TODO: We should use `ParamSpec` here, but mypy doesn't support it yet.
|
28
|
+
# Check https://github.com/python/mypy/issues/12278 for more details.
|
29
|
+
async def run_in_threadpool(
|
30
|
+
func: typing.Callable[..., T], *args: typing.Any, **kwargs: typing.Any
|
31
|
+
) -> T:
|
32
|
+
if kwargs: # pragma: no cover
|
33
|
+
# run_sync doesn't accept 'kwargs', so bind them in here
|
34
|
+
func = functools.partial(func, **kwargs)
|
35
|
+
return await anyio.to_thread.run_sync(func, *args)
|
36
|
+
|
37
|
+
|
38
|
+
class _StopIteration(Exception):
|
39
|
+
pass
|
40
|
+
|
41
|
+
|
42
|
+
def _next(iterator: typing.Iterator[T]) -> T:
|
43
|
+
# We can't raise `StopIteration` from within the threadpool iterator
|
44
|
+
# and catch it outside that context, so we coerce them into a different
|
45
|
+
# exception type.
|
46
|
+
try:
|
47
|
+
return next(iterator)
|
48
|
+
except StopIteration:
|
49
|
+
raise _StopIteration
|
50
|
+
|
51
|
+
|
52
|
+
async def iterate_in_threadpool(
|
53
|
+
iterator: typing.Iterator[T],
|
54
|
+
) -> typing.AsyncIterator[T]:
|
55
|
+
while True:
|
56
|
+
try:
|
57
|
+
yield await anyio.to_thread.run_sync(_next, iterator)
|
58
|
+
except _StopIteration:
|
59
|
+
break
|
@@ -0,0 +1,151 @@
|
|
1
|
+
import os
|
2
|
+
import typing
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
|
6
|
+
class undefined:
|
7
|
+
pass
|
8
|
+
|
9
|
+
|
10
|
+
class EnvironError(Exception):
|
11
|
+
pass
|
12
|
+
|
13
|
+
|
14
|
+
class Environ(typing.MutableMapping[str, str]):
|
15
|
+
def __init__(self, environ: typing.MutableMapping[str, str] = os.environ):
|
16
|
+
self._environ = environ
|
17
|
+
self._has_been_read: typing.Set[str] = set()
|
18
|
+
|
19
|
+
def __getitem__(self, key: str) -> str:
|
20
|
+
self._has_been_read.add(key)
|
21
|
+
return self._environ.__getitem__(key)
|
22
|
+
|
23
|
+
def __setitem__(self, key: str, value: str) -> None:
|
24
|
+
if key in self._has_been_read:
|
25
|
+
raise EnvironError(
|
26
|
+
f"Attempting to set environ['{key}'], but the value has already been "
|
27
|
+
"read."
|
28
|
+
)
|
29
|
+
self._environ.__setitem__(key, value)
|
30
|
+
|
31
|
+
def __delitem__(self, key: str) -> None:
|
32
|
+
if key in self._has_been_read:
|
33
|
+
raise EnvironError(
|
34
|
+
f"Attempting to delete environ['{key}'], but the value has already "
|
35
|
+
"been read."
|
36
|
+
)
|
37
|
+
self._environ.__delitem__(key)
|
38
|
+
|
39
|
+
def __iter__(self) -> typing.Iterator[str]:
|
40
|
+
return iter(self._environ)
|
41
|
+
|
42
|
+
def __len__(self) -> int:
|
43
|
+
return len(self._environ)
|
44
|
+
|
45
|
+
|
46
|
+
environ = Environ()
|
47
|
+
|
48
|
+
T = typing.TypeVar("T")
|
49
|
+
|
50
|
+
|
51
|
+
class Config:
|
52
|
+
def __init__(
|
53
|
+
self,
|
54
|
+
env_file: typing.Optional[typing.Union[str, Path]] = None,
|
55
|
+
environ: typing.Mapping[str, str] = environ,
|
56
|
+
env_prefix: str = "",
|
57
|
+
) -> None:
|
58
|
+
self.environ = environ
|
59
|
+
self.env_prefix = env_prefix
|
60
|
+
self.file_values: typing.Dict[str, str] = {}
|
61
|
+
if env_file is not None and os.path.isfile(env_file):
|
62
|
+
self.file_values = self._read_file(env_file)
|
63
|
+
|
64
|
+
@typing.overload
|
65
|
+
def __call__(self, key: str, *, default: None) -> typing.Optional[str]:
|
66
|
+
...
|
67
|
+
|
68
|
+
@typing.overload
|
69
|
+
def __call__(self, key: str, cast: typing.Type[T], default: T = ...) -> T:
|
70
|
+
...
|
71
|
+
|
72
|
+
@typing.overload
|
73
|
+
def __call__(
|
74
|
+
self, key: str, cast: typing.Type[str] = ..., default: str = ...
|
75
|
+
) -> str:
|
76
|
+
...
|
77
|
+
|
78
|
+
@typing.overload
|
79
|
+
def __call__(
|
80
|
+
self,
|
81
|
+
key: str,
|
82
|
+
cast: typing.Callable[[typing.Any], T] = ...,
|
83
|
+
default: typing.Any = ...,
|
84
|
+
) -> T:
|
85
|
+
...
|
86
|
+
|
87
|
+
@typing.overload
|
88
|
+
def __call__(
|
89
|
+
self, key: str, cast: typing.Type[str] = ..., default: T = ...
|
90
|
+
) -> typing.Union[T, str]:
|
91
|
+
...
|
92
|
+
|
93
|
+
def __call__(
|
94
|
+
self,
|
95
|
+
key: str,
|
96
|
+
cast: typing.Optional[typing.Callable[[typing.Any], typing.Any]] = None,
|
97
|
+
default: typing.Any = undefined,
|
98
|
+
) -> typing.Any:
|
99
|
+
return self.get(key, cast, default)
|
100
|
+
|
101
|
+
def get(
|
102
|
+
self,
|
103
|
+
key: str,
|
104
|
+
cast: typing.Optional[typing.Callable[[typing.Any], typing.Any]] = None,
|
105
|
+
default: typing.Any = undefined,
|
106
|
+
) -> typing.Any:
|
107
|
+
key = self.env_prefix + key
|
108
|
+
if key in self.environ:
|
109
|
+
value = self.environ[key]
|
110
|
+
return self._perform_cast(key, value, cast)
|
111
|
+
if key in self.file_values:
|
112
|
+
value = self.file_values[key]
|
113
|
+
return self._perform_cast(key, value, cast)
|
114
|
+
if default is not undefined:
|
115
|
+
return self._perform_cast(key, default, cast)
|
116
|
+
raise KeyError(f"Config '{key}' is missing, and has no default.")
|
117
|
+
|
118
|
+
def _read_file(self, file_name: typing.Union[str, Path]) -> typing.Dict[str, str]:
|
119
|
+
file_values: typing.Dict[str, str] = {}
|
120
|
+
with open(file_name) as input_file:
|
121
|
+
for line in input_file.readlines():
|
122
|
+
line = line.strip()
|
123
|
+
if "=" in line and not line.startswith("#"):
|
124
|
+
key, value = line.split("=", 1)
|
125
|
+
key = key.strip()
|
126
|
+
value = value.strip().strip("\"'")
|
127
|
+
file_values[key] = value
|
128
|
+
return file_values
|
129
|
+
|
130
|
+
def _perform_cast(
|
131
|
+
self,
|
132
|
+
key: str,
|
133
|
+
value: typing.Any,
|
134
|
+
cast: typing.Optional[typing.Callable[[typing.Any], typing.Any]] = None,
|
135
|
+
) -> typing.Any:
|
136
|
+
if cast is None or value is None:
|
137
|
+
return value
|
138
|
+
elif cast is bool and isinstance(value, str):
|
139
|
+
mapping = {"true": True, "1": True, "false": False, "0": False}
|
140
|
+
value = value.lower()
|
141
|
+
if value not in mapping:
|
142
|
+
raise ValueError(
|
143
|
+
f"Config '{key}' has value '{value}'. Not a valid bool."
|
144
|
+
)
|
145
|
+
return mapping[value]
|
146
|
+
try:
|
147
|
+
return cast(value)
|
148
|
+
except (TypeError, ValueError):
|
149
|
+
raise ValueError(
|
150
|
+
f"Config '{key}' has value '{value}'. Not a valid {cast.__name__}."
|
151
|
+
)
|
@@ -0,0 +1,87 @@
|
|
1
|
+
import math
|
2
|
+
import typing
|
3
|
+
import uuid
|
4
|
+
|
5
|
+
T = typing.TypeVar("T")
|
6
|
+
|
7
|
+
|
8
|
+
class Convertor(typing.Generic[T]):
|
9
|
+
regex: typing.ClassVar[str] = ""
|
10
|
+
|
11
|
+
def convert(self, value: str) -> T:
|
12
|
+
raise NotImplementedError() # pragma: no cover
|
13
|
+
|
14
|
+
def to_string(self, value: T) -> str:
|
15
|
+
raise NotImplementedError() # pragma: no cover
|
16
|
+
|
17
|
+
|
18
|
+
class StringConvertor(Convertor[str]):
|
19
|
+
regex = "[^/]+"
|
20
|
+
|
21
|
+
def convert(self, value: str) -> str:
|
22
|
+
return value
|
23
|
+
|
24
|
+
def to_string(self, value: str) -> str:
|
25
|
+
value = str(value)
|
26
|
+
assert "/" not in value, "May not contain path separators"
|
27
|
+
assert value, "Must not be empty"
|
28
|
+
return value
|
29
|
+
|
30
|
+
|
31
|
+
class PathConvertor(Convertor[str]):
|
32
|
+
regex = ".*"
|
33
|
+
|
34
|
+
def convert(self, value: str) -> str:
|
35
|
+
return str(value)
|
36
|
+
|
37
|
+
def to_string(self, value: str) -> str:
|
38
|
+
return str(value)
|
39
|
+
|
40
|
+
|
41
|
+
class IntegerConvertor(Convertor[int]):
|
42
|
+
regex = "[0-9]+"
|
43
|
+
|
44
|
+
def convert(self, value: str) -> int:
|
45
|
+
return int(value)
|
46
|
+
|
47
|
+
def to_string(self, value: int) -> str:
|
48
|
+
value = int(value)
|
49
|
+
assert value >= 0, "Negative integers are not supported"
|
50
|
+
return str(value)
|
51
|
+
|
52
|
+
|
53
|
+
class FloatConvertor(Convertor[float]):
|
54
|
+
regex = r"[0-9]+(\.[0-9]+)?"
|
55
|
+
|
56
|
+
def convert(self, value: str) -> float:
|
57
|
+
return float(value)
|
58
|
+
|
59
|
+
def to_string(self, value: float) -> str:
|
60
|
+
value = float(value)
|
61
|
+
assert value >= 0.0, "Negative floats are not supported"
|
62
|
+
assert not math.isnan(value), "NaN values are not supported"
|
63
|
+
assert not math.isinf(value), "Infinite values are not supported"
|
64
|
+
return ("%0.20f" % value).rstrip("0").rstrip(".")
|
65
|
+
|
66
|
+
|
67
|
+
class UUIDConvertor(Convertor[uuid.UUID]):
|
68
|
+
regex = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
69
|
+
|
70
|
+
def convert(self, value: str) -> uuid.UUID:
|
71
|
+
return uuid.UUID(value)
|
72
|
+
|
73
|
+
def to_string(self, value: uuid.UUID) -> str:
|
74
|
+
return str(value)
|
75
|
+
|
76
|
+
|
77
|
+
CONVERTOR_TYPES: typing.Dict[str, Convertor[typing.Any]] = {
|
78
|
+
"str": StringConvertor(),
|
79
|
+
"path": PathConvertor(),
|
80
|
+
"int": IntegerConvertor(),
|
81
|
+
"float": FloatConvertor(),
|
82
|
+
"uuid": UUIDConvertor(),
|
83
|
+
}
|
84
|
+
|
85
|
+
|
86
|
+
def register_url_convertor(key: str, convertor: Convertor[typing.Any]) -> None:
|
87
|
+
CONVERTOR_TYPES[key] = convertor
|