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.
Files changed (36) hide show
  1. prefect/_internal/compatibility/deprecated.py +0 -53
  2. prefect/blocks/core.py +132 -4
  3. prefect/blocks/notifications.py +26 -3
  4. prefect/client/base.py +30 -24
  5. prefect/client/orchestration.py +121 -47
  6. prefect/client/utilities.py +4 -4
  7. prefect/concurrency/asyncio.py +48 -7
  8. prefect/concurrency/context.py +24 -0
  9. prefect/concurrency/services.py +24 -8
  10. prefect/concurrency/sync.py +30 -3
  11. prefect/context.py +85 -24
  12. prefect/events/clients.py +93 -60
  13. prefect/events/utilities.py +0 -2
  14. prefect/events/worker.py +9 -2
  15. prefect/flow_engine.py +6 -3
  16. prefect/flows.py +176 -12
  17. prefect/futures.py +84 -7
  18. prefect/profiles.toml +16 -2
  19. prefect/runner/runner.py +6 -1
  20. prefect/runner/storage.py +4 -0
  21. prefect/settings.py +108 -14
  22. prefect/task_engine.py +901 -285
  23. prefect/task_runs.py +24 -1
  24. prefect/task_worker.py +7 -1
  25. prefect/tasks.py +9 -5
  26. prefect/utilities/asyncutils.py +0 -6
  27. prefect/utilities/callables.py +5 -3
  28. prefect/utilities/engine.py +3 -0
  29. prefect/utilities/importtools.py +138 -58
  30. prefect/utilities/schema_tools/validation.py +30 -0
  31. prefect/utilities/services.py +32 -0
  32. {prefect_client-3.0.0rc13.dist-info → prefect_client-3.0.0rc15.dist-info}/METADATA +39 -39
  33. {prefect_client-3.0.0rc13.dist-info → prefect_client-3.0.0rc15.dist-info}/RECORD +36 -35
  34. {prefect_client-3.0.0rc13.dist-info → prefect_client-3.0.0rc15.dist-info}/WHEEL +1 -1
  35. {prefect_client-3.0.0rc13.dist-info → prefect_client-3.0.0rc15.dist-info}/LICENSE +0 -0
  36. {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
- @deprecated_async_method
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
- active = "default"
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
- [profiles.default]
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 critical_service_loop
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
- from prefect.context import SettingsContext
2222
+ import prefect.context
2129
2223
 
2130
2224
  profiles = load_profiles()
2131
- context = SettingsContext.get()
2225
+ context = prefect.context.get_settings_context()
2132
2226
 
2133
2227
  if context:
2134
2228
  profiles.set_active(context.profile.name)