prefect-client 3.0.0rc13__py3-none-any.whl → 3.0.0rc15__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/compatibility/deprecated.py +0 -53
- prefect/blocks/core.py +132 -4
- prefect/blocks/notifications.py +26 -3
- prefect/client/base.py +30 -24
- prefect/client/orchestration.py +121 -47
- prefect/client/utilities.py +4 -4
- prefect/concurrency/asyncio.py +48 -7
- prefect/concurrency/context.py +24 -0
- prefect/concurrency/services.py +24 -8
- prefect/concurrency/sync.py +30 -3
- prefect/context.py +85 -24
- prefect/events/clients.py +93 -60
- prefect/events/utilities.py +0 -2
- prefect/events/worker.py +9 -2
- prefect/flow_engine.py +6 -3
- prefect/flows.py +176 -12
- prefect/futures.py +84 -7
- prefect/profiles.toml +16 -2
- prefect/runner/runner.py +6 -1
- prefect/runner/storage.py +4 -0
- prefect/settings.py +108 -14
- prefect/task_engine.py +901 -285
- prefect/task_runs.py +24 -1
- prefect/task_worker.py +7 -1
- prefect/tasks.py +9 -5
- prefect/utilities/asyncutils.py +0 -6
- prefect/utilities/callables.py +5 -3
- prefect/utilities/engine.py +3 -0
- prefect/utilities/importtools.py +138 -58
- prefect/utilities/schema_tools/validation.py +30 -0
- prefect/utilities/services.py +32 -0
- {prefect_client-3.0.0rc13.dist-info → prefect_client-3.0.0rc15.dist-info}/METADATA +39 -39
- {prefect_client-3.0.0rc13.dist-info → prefect_client-3.0.0rc15.dist-info}/RECORD +36 -35
- {prefect_client-3.0.0rc13.dist-info → prefect_client-3.0.0rc15.dist-info}/WHEEL +1 -1
- {prefect_client-3.0.0rc13.dist-info → prefect_client-3.0.0rc15.dist-info}/LICENSE +0 -0
- {prefect_client-3.0.0rc13.dist-info → prefect_client-3.0.0rc15.dist-info}/top_level.txt +0 -0
prefect/futures.py
CHANGED
@@ -2,14 +2,14 @@ import abc
|
|
2
2
|
import collections
|
3
3
|
import concurrent.futures
|
4
4
|
import inspect
|
5
|
+
import threading
|
5
6
|
import uuid
|
6
|
-
from collections.abc import Iterator
|
7
|
+
from collections.abc import Generator, Iterator
|
7
8
|
from functools import partial
|
8
|
-
from typing import Any, Generic, List, Optional, Set, Union, cast
|
9
|
+
from typing import Any, Callable, Generic, List, Optional, Set, Union, cast
|
9
10
|
|
10
11
|
from typing_extensions import TypeVar
|
11
12
|
|
12
|
-
from prefect._internal.compatibility.deprecated import deprecated_async_method
|
13
13
|
from prefect.client.orchestration import get_client
|
14
14
|
from prefect.client.schemas.objects import TaskRun
|
15
15
|
from prefect.exceptions import ObjectNotFound
|
@@ -91,6 +91,16 @@ class PrefectFuture(abc.ABC, Generic[R]):
|
|
91
91
|
The result of the task run.
|
92
92
|
"""
|
93
93
|
|
94
|
+
@abc.abstractmethod
|
95
|
+
def add_done_callback(self, fn):
|
96
|
+
"""
|
97
|
+
Add a callback to be run when the future completes or is cancelled.
|
98
|
+
|
99
|
+
Args:
|
100
|
+
fn: A callable that will be called with this future as its only argument when the future completes or is cancelled.
|
101
|
+
"""
|
102
|
+
...
|
103
|
+
|
94
104
|
|
95
105
|
class PrefectWrappedFuture(PrefectFuture, abc.ABC, Generic[R, F]):
|
96
106
|
"""
|
@@ -106,6 +116,17 @@ class PrefectWrappedFuture(PrefectFuture, abc.ABC, Generic[R, F]):
|
|
106
116
|
"""The underlying future object wrapped by this Prefect future"""
|
107
117
|
return self._wrapped_future
|
108
118
|
|
119
|
+
def add_done_callback(self, fn: Callable[[PrefectFuture], None]):
|
120
|
+
if not self._final_state:
|
121
|
+
|
122
|
+
def call_with_self(future):
|
123
|
+
"""Call the callback with self as the argument, this is necessary to ensure we remove the future from the pending set"""
|
124
|
+
fn(self)
|
125
|
+
|
126
|
+
self._wrapped_future.add_done_callback(call_with_self)
|
127
|
+
return
|
128
|
+
fn(self)
|
129
|
+
|
109
130
|
|
110
131
|
class PrefectConcurrentFuture(PrefectWrappedFuture[R, concurrent.futures.Future]):
|
111
132
|
"""
|
@@ -113,7 +134,6 @@ class PrefectConcurrentFuture(PrefectWrappedFuture[R, concurrent.futures.Future]
|
|
113
134
|
when the task run is submitted to a ThreadPoolExecutor.
|
114
135
|
"""
|
115
136
|
|
116
|
-
@deprecated_async_method
|
117
137
|
def wait(self, timeout: Optional[float] = None) -> None:
|
118
138
|
try:
|
119
139
|
result = self._wrapped_future.result(timeout=timeout)
|
@@ -122,7 +142,6 @@ class PrefectConcurrentFuture(PrefectWrappedFuture[R, concurrent.futures.Future]
|
|
122
142
|
if isinstance(result, State):
|
123
143
|
self._final_state = result
|
124
144
|
|
125
|
-
@deprecated_async_method
|
126
145
|
def result(
|
127
146
|
self,
|
128
147
|
timeout: Optional[float] = None,
|
@@ -138,6 +157,7 @@ class PrefectConcurrentFuture(PrefectWrappedFuture[R, concurrent.futures.Future]
|
|
138
157
|
|
139
158
|
if isinstance(future_result, State):
|
140
159
|
self._final_state = future_result
|
160
|
+
|
141
161
|
else:
|
142
162
|
return future_result
|
143
163
|
|
@@ -172,7 +192,9 @@ class PrefectDistributedFuture(PrefectFuture[R]):
|
|
172
192
|
any task run scheduled in Prefect's API.
|
173
193
|
"""
|
174
194
|
|
175
|
-
|
195
|
+
done_callbacks: List[Callable[[PrefectFuture], None]] = []
|
196
|
+
waiter = None
|
197
|
+
|
176
198
|
def wait(self, timeout: Optional[float] = None) -> None:
|
177
199
|
return run_coro_as_sync(self.wait_async(timeout=timeout))
|
178
200
|
|
@@ -209,7 +231,6 @@ class PrefectDistributedFuture(PrefectFuture[R]):
|
|
209
231
|
self._final_state = task_run.state
|
210
232
|
return
|
211
233
|
|
212
|
-
@deprecated_async_method
|
213
234
|
def result(
|
214
235
|
self,
|
215
236
|
timeout: Optional[float] = None,
|
@@ -235,11 +256,27 @@ class PrefectDistributedFuture(PrefectFuture[R]):
|
|
235
256
|
raise_on_failure=raise_on_failure, fetch=True
|
236
257
|
)
|
237
258
|
|
259
|
+
def add_done_callback(self, fn: Callable[[PrefectFuture], None]):
|
260
|
+
if self._final_state:
|
261
|
+
fn(self)
|
262
|
+
return
|
263
|
+
TaskRunWaiter.instance()
|
264
|
+
with get_client(sync_client=True) as client:
|
265
|
+
task_run = client.read_task_run(task_run_id=self._task_run_id)
|
266
|
+
if task_run.state.is_final():
|
267
|
+
self._final_state = task_run.state
|
268
|
+
fn(self)
|
269
|
+
return
|
270
|
+
TaskRunWaiter.add_done_callback(self._task_run_id, partial(fn, self))
|
271
|
+
|
238
272
|
def __eq__(self, other):
|
239
273
|
if not isinstance(other, PrefectDistributedFuture):
|
240
274
|
return False
|
241
275
|
return self.task_run_id == other.task_run_id
|
242
276
|
|
277
|
+
def __hash__(self):
|
278
|
+
return hash(self.task_run_id)
|
279
|
+
|
243
280
|
|
244
281
|
class PrefectFutureList(list, Iterator, Generic[F]):
|
245
282
|
"""
|
@@ -292,6 +329,46 @@ class PrefectFutureList(list, Iterator, Generic[F]):
|
|
292
329
|
) from exc
|
293
330
|
|
294
331
|
|
332
|
+
def as_completed(
|
333
|
+
futures: List[PrefectFuture], timeout: Optional[float] = None
|
334
|
+
) -> Generator[PrefectFuture, None]:
|
335
|
+
unique_futures: Set[PrefectFuture] = set(futures)
|
336
|
+
total_futures = len(unique_futures)
|
337
|
+
try:
|
338
|
+
with timeout_context(timeout):
|
339
|
+
done = {f for f in unique_futures if f._final_state}
|
340
|
+
pending = unique_futures - done
|
341
|
+
yield from done
|
342
|
+
|
343
|
+
finished_event = threading.Event()
|
344
|
+
finished_lock = threading.Lock()
|
345
|
+
finished_futures = []
|
346
|
+
|
347
|
+
def add_to_done(future):
|
348
|
+
with finished_lock:
|
349
|
+
finished_futures.append(future)
|
350
|
+
finished_event.set()
|
351
|
+
|
352
|
+
for future in pending:
|
353
|
+
future.add_done_callback(add_to_done)
|
354
|
+
|
355
|
+
while pending:
|
356
|
+
finished_event.wait()
|
357
|
+
with finished_lock:
|
358
|
+
done = finished_futures
|
359
|
+
finished_futures = []
|
360
|
+
finished_event.clear()
|
361
|
+
|
362
|
+
for future in done:
|
363
|
+
pending.remove(future)
|
364
|
+
yield future
|
365
|
+
|
366
|
+
except TimeoutError:
|
367
|
+
raise TimeoutError(
|
368
|
+
"%d (of %d) futures unfinished" % (len(pending), total_futures)
|
369
|
+
)
|
370
|
+
|
371
|
+
|
295
372
|
DoneAndNotDoneFutures = collections.namedtuple("DoneAndNotDoneFutures", "done not_done")
|
296
373
|
|
297
374
|
|
prefect/profiles.toml
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
-
|
1
|
+
# This is a template for profile configuration for Prefect.
|
2
|
+
# You can modify these profiles or create new ones to suit your needs.
|
2
3
|
|
3
|
-
|
4
|
+
active = "ephemeral"
|
5
|
+
|
6
|
+
[profiles.ephemeral]
|
7
|
+
PREFECT_SERVER_ALLOW_EPHEMERAL_MODE = "true"
|
8
|
+
|
9
|
+
[profiles.local]
|
10
|
+
# You will need to set these values appropriately for your local development environment
|
11
|
+
PREFECT_API_URL = "http://127.0.0.1:4200/api"
|
12
|
+
|
13
|
+
[profiles.test]
|
14
|
+
PREFECT_SERVER_ALLOW_EPHEMERAL_MODE = "true"
|
15
|
+
PREFECT_API_DATABASE_CONNECTION_URL = "sqlite+aiosqlite:///:memory:"
|
16
|
+
|
17
|
+
[profiles.cloud]
|
prefect/runner/runner.py
CHANGED
@@ -92,7 +92,10 @@ from prefect.utilities.asyncutils import (
|
|
92
92
|
)
|
93
93
|
from prefect.utilities.engine import propose_state
|
94
94
|
from prefect.utilities.processutils import _register_signal, run_process
|
95
|
-
from prefect.utilities.services import
|
95
|
+
from prefect.utilities.services import (
|
96
|
+
critical_service_loop,
|
97
|
+
start_client_metrics_server,
|
98
|
+
)
|
96
99
|
from prefect.utilities.slugify import slugify
|
97
100
|
|
98
101
|
if TYPE_CHECKING:
|
@@ -380,6 +383,8 @@ class Runner:
|
|
380
383
|
)
|
381
384
|
server_thread.start()
|
382
385
|
|
386
|
+
start_client_metrics_server()
|
387
|
+
|
383
388
|
async with self as runner:
|
384
389
|
async with self._loops_task_group as tg:
|
385
390
|
for storage in self._storage_objs:
|
prefect/runner/storage.py
CHANGED
@@ -280,6 +280,10 @@ class GitRepository:
|
|
280
280
|
"branch": self._branch,
|
281
281
|
}
|
282
282
|
}
|
283
|
+
if self._include_submodules:
|
284
|
+
pull_step["prefect.deployments.steps.git_clone"][
|
285
|
+
"include_submodules"
|
286
|
+
] = self._include_submodules
|
283
287
|
if isinstance(self._credentials, Block):
|
284
288
|
pull_step["prefect.deployments.steps.git_clone"][
|
285
289
|
"credentials"
|
prefect/settings.py
CHANGED
@@ -351,6 +351,7 @@ def warn_on_database_password_value_without_usage(values):
|
|
351
351
|
if (
|
352
352
|
value
|
353
353
|
and not value.startswith(OBFUSCATED_PREFIX)
|
354
|
+
and values["PREFECT_API_DATABASE_CONNECTION_URL"] is not None
|
354
355
|
and (
|
355
356
|
"PREFECT_API_DATABASE_PASSWORD"
|
356
357
|
not in values["PREFECT_API_DATABASE_CONNECTION_URL"]
|
@@ -403,7 +404,38 @@ def warn_on_misconfigured_api_url(values):
|
|
403
404
|
return values
|
404
405
|
|
405
406
|
|
406
|
-
def default_database_connection_url(settings, value):
|
407
|
+
def default_database_connection_url(settings: "Settings", value: Optional[str]):
|
408
|
+
driver = PREFECT_API_DATABASE_DRIVER.value_from(settings)
|
409
|
+
if driver == "postgresql+asyncpg":
|
410
|
+
required = [
|
411
|
+
PREFECT_API_DATABASE_HOST,
|
412
|
+
PREFECT_API_DATABASE_USER,
|
413
|
+
PREFECT_API_DATABASE_NAME,
|
414
|
+
PREFECT_API_DATABASE_PASSWORD,
|
415
|
+
]
|
416
|
+
missing = [
|
417
|
+
setting.name for setting in required if not setting.value_from(settings)
|
418
|
+
]
|
419
|
+
if missing:
|
420
|
+
raise ValueError(
|
421
|
+
f"Missing required database connection settings: {', '.join(missing)}"
|
422
|
+
)
|
423
|
+
|
424
|
+
host = PREFECT_API_DATABASE_HOST.value_from(settings)
|
425
|
+
port = PREFECT_API_DATABASE_PORT.value_from(settings) or 5432
|
426
|
+
user = PREFECT_API_DATABASE_USER.value_from(settings)
|
427
|
+
name = PREFECT_API_DATABASE_NAME.value_from(settings)
|
428
|
+
password = PREFECT_API_DATABASE_PASSWORD.value_from(settings)
|
429
|
+
|
430
|
+
return f"{driver}://{user}:{password}@{host}:{port}/{name}"
|
431
|
+
|
432
|
+
elif driver == "sqlite+aiosqlite":
|
433
|
+
path = PREFECT_API_DATABASE_NAME.value_from(settings)
|
434
|
+
if path:
|
435
|
+
return f"{driver}:///{path}"
|
436
|
+
elif driver:
|
437
|
+
raise ValueError(f"Unsupported database driver: {driver}")
|
438
|
+
|
407
439
|
templater = template_with_settings(PREFECT_HOME, PREFECT_API_DATABASE_PASSWORD)
|
408
440
|
|
409
441
|
# If the user has provided a value, use it
|
@@ -941,17 +973,6 @@ backend on application startup. If not set, block types must be manually
|
|
941
973
|
registered.
|
942
974
|
"""
|
943
975
|
|
944
|
-
PREFECT_API_DATABASE_PASSWORD = Setting(
|
945
|
-
Optional[str],
|
946
|
-
default=None,
|
947
|
-
is_secret=True,
|
948
|
-
)
|
949
|
-
"""
|
950
|
-
Password to template into the `PREFECT_API_DATABASE_CONNECTION_URL`.
|
951
|
-
This is useful if the password must be provided separately from the connection URL.
|
952
|
-
To use this setting, you must include it in your connection URL.
|
953
|
-
"""
|
954
|
-
|
955
976
|
PREFECT_API_DATABASE_CONNECTION_URL = Setting(
|
956
977
|
Optional[str],
|
957
978
|
default=None,
|
@@ -981,6 +1002,46 @@ PREFECT_API_DATABASE_CONNECTION_URL='postgresql+asyncpg://postgres:${PREFECT_API
|
|
981
1002
|
```
|
982
1003
|
"""
|
983
1004
|
|
1005
|
+
PREFECT_API_DATABASE_DRIVER = Setting(
|
1006
|
+
Optional[Literal["postgresql+asyncpg", "sqlite+aiosqlite"]],
|
1007
|
+
default=None,
|
1008
|
+
)
|
1009
|
+
"""
|
1010
|
+
The database driver to use when connecting to the database.
|
1011
|
+
"""
|
1012
|
+
|
1013
|
+
PREFECT_API_DATABASE_HOST = Setting(Optional[str], default=None)
|
1014
|
+
"""
|
1015
|
+
The database server host.
|
1016
|
+
"""
|
1017
|
+
|
1018
|
+
PREFECT_API_DATABASE_PORT = Setting(Optional[int], default=None)
|
1019
|
+
"""
|
1020
|
+
The database server port.
|
1021
|
+
"""
|
1022
|
+
|
1023
|
+
PREFECT_API_DATABASE_USER = Setting(Optional[str], default=None)
|
1024
|
+
"""
|
1025
|
+
The user to use when connecting to the database.
|
1026
|
+
"""
|
1027
|
+
|
1028
|
+
PREFECT_API_DATABASE_NAME = Setting(Optional[str], default=None)
|
1029
|
+
"""
|
1030
|
+
The name of the Prefect database on the remote server, or the path to the database file
|
1031
|
+
for SQLite.
|
1032
|
+
"""
|
1033
|
+
|
1034
|
+
PREFECT_API_DATABASE_PASSWORD = Setting(
|
1035
|
+
Optional[str],
|
1036
|
+
default=None,
|
1037
|
+
is_secret=True,
|
1038
|
+
)
|
1039
|
+
"""
|
1040
|
+
Password to template into the `PREFECT_API_DATABASE_CONNECTION_URL`.
|
1041
|
+
This is useful if the password must be provided separately from the connection URL.
|
1042
|
+
To use this setting, you must include it in your connection URL.
|
1043
|
+
"""
|
1044
|
+
|
984
1045
|
PREFECT_API_DATABASE_ECHO = Setting(
|
985
1046
|
bool,
|
986
1047
|
default=False,
|
@@ -1216,6 +1277,19 @@ compromise. Adjust this setting based on your specific security requirements
|
|
1216
1277
|
and usage patterns.
|
1217
1278
|
"""
|
1218
1279
|
|
1280
|
+
PREFECT_SERVER_ALLOW_EPHEMERAL_MODE = Setting(bool, default=False)
|
1281
|
+
"""
|
1282
|
+
Controls whether or not a subprocess server can be started when no API URL is provided.
|
1283
|
+
"""
|
1284
|
+
|
1285
|
+
PREFECT_SERVER_EPHEMERAL_STARTUP_TIMEOUT_SECONDS = Setting(
|
1286
|
+
int,
|
1287
|
+
default=10,
|
1288
|
+
)
|
1289
|
+
"""
|
1290
|
+
The number of seconds to wait for an ephemeral server to respond on start up before erroring.
|
1291
|
+
"""
|
1292
|
+
|
1219
1293
|
PREFECT_UI_ENABLED = Setting(
|
1220
1294
|
bool,
|
1221
1295
|
default=True,
|
@@ -1561,6 +1635,26 @@ The page size for the queries to backfill events for websocket subscribers
|
|
1561
1635
|
"""
|
1562
1636
|
|
1563
1637
|
|
1638
|
+
# Metrics settings
|
1639
|
+
|
1640
|
+
PREFECT_API_ENABLE_METRICS = Setting(bool, default=False)
|
1641
|
+
"""
|
1642
|
+
Whether or not to enable Prometheus metrics in the server application. Metrics are
|
1643
|
+
served at the path /api/metrics on the API server.
|
1644
|
+
"""
|
1645
|
+
|
1646
|
+
PREFECT_CLIENT_ENABLE_METRICS = Setting(bool, default=False)
|
1647
|
+
"""
|
1648
|
+
Whether or not to enable Prometheus metrics in the client SDK. Metrics are served
|
1649
|
+
at the path /metrics.
|
1650
|
+
"""
|
1651
|
+
|
1652
|
+
PREFECT_CLIENT_METRICS_PORT = Setting(int, default=4201)
|
1653
|
+
"""
|
1654
|
+
The port to expose the client Prometheus metrics on.
|
1655
|
+
"""
|
1656
|
+
|
1657
|
+
|
1564
1658
|
# Deprecated settings ------------------------------------------------------------------
|
1565
1659
|
|
1566
1660
|
|
@@ -2125,10 +2219,10 @@ def load_current_profile():
|
|
2125
2219
|
This will _not_ include settings from the current settings context. Only settings
|
2126
2220
|
that have been persisted to the profiles file will be saved.
|
2127
2221
|
"""
|
2128
|
-
|
2222
|
+
import prefect.context
|
2129
2223
|
|
2130
2224
|
profiles = load_profiles()
|
2131
|
-
context =
|
2225
|
+
context = prefect.context.get_settings_context()
|
2132
2226
|
|
2133
2227
|
if context:
|
2134
2228
|
profiles.set_active(context.profile.name)
|