prefect-client 2.19.3__py3-none-any.whl → 3.0.0rc1__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 (239) hide show
  1. prefect/__init__.py +8 -56
  2. prefect/_internal/compatibility/deprecated.py +6 -115
  3. prefect/_internal/compatibility/experimental.py +4 -79
  4. prefect/_internal/concurrency/api.py +0 -34
  5. prefect/_internal/concurrency/calls.py +0 -6
  6. prefect/_internal/concurrency/cancellation.py +0 -3
  7. prefect/_internal/concurrency/event_loop.py +0 -20
  8. prefect/_internal/concurrency/inspection.py +3 -3
  9. prefect/_internal/concurrency/threads.py +35 -0
  10. prefect/_internal/concurrency/waiters.py +0 -28
  11. prefect/_internal/pydantic/__init__.py +0 -45
  12. prefect/_internal/pydantic/v1_schema.py +21 -22
  13. prefect/_internal/pydantic/v2_schema.py +0 -2
  14. prefect/_internal/pydantic/v2_validated_func.py +18 -23
  15. prefect/_internal/schemas/bases.py +44 -177
  16. prefect/_internal/schemas/fields.py +1 -43
  17. prefect/_internal/schemas/validators.py +60 -158
  18. prefect/artifacts.py +161 -14
  19. prefect/automations.py +39 -4
  20. prefect/blocks/abstract.py +1 -1
  21. prefect/blocks/core.py +268 -148
  22. prefect/blocks/fields.py +2 -57
  23. prefect/blocks/kubernetes.py +8 -12
  24. prefect/blocks/notifications.py +40 -20
  25. prefect/blocks/system.py +22 -11
  26. prefect/blocks/webhook.py +2 -9
  27. prefect/client/base.py +4 -4
  28. prefect/client/cloud.py +8 -13
  29. prefect/client/orchestration.py +347 -341
  30. prefect/client/schemas/actions.py +92 -86
  31. prefect/client/schemas/filters.py +20 -40
  32. prefect/client/schemas/objects.py +147 -145
  33. prefect/client/schemas/responses.py +16 -24
  34. prefect/client/schemas/schedules.py +47 -35
  35. prefect/client/subscriptions.py +2 -2
  36. prefect/client/utilities.py +5 -2
  37. prefect/concurrency/asyncio.py +3 -1
  38. prefect/concurrency/events.py +1 -1
  39. prefect/concurrency/services.py +6 -3
  40. prefect/context.py +195 -27
  41. prefect/deployments/__init__.py +5 -6
  42. prefect/deployments/base.py +7 -5
  43. prefect/deployments/flow_runs.py +185 -0
  44. prefect/deployments/runner.py +50 -45
  45. prefect/deployments/schedules.py +28 -23
  46. prefect/deployments/steps/__init__.py +0 -1
  47. prefect/deployments/steps/core.py +1 -0
  48. prefect/deployments/steps/pull.py +7 -21
  49. prefect/engine.py +12 -2422
  50. prefect/events/actions.py +17 -23
  51. prefect/events/cli/automations.py +19 -6
  52. prefect/events/clients.py +14 -37
  53. prefect/events/filters.py +14 -18
  54. prefect/events/related.py +2 -2
  55. prefect/events/schemas/__init__.py +0 -5
  56. prefect/events/schemas/automations.py +55 -46
  57. prefect/events/schemas/deployment_triggers.py +7 -197
  58. prefect/events/schemas/events.py +34 -65
  59. prefect/events/schemas/labelling.py +10 -14
  60. prefect/events/utilities.py +2 -3
  61. prefect/events/worker.py +2 -3
  62. prefect/filesystems.py +6 -517
  63. prefect/{new_flow_engine.py → flow_engine.py} +313 -72
  64. prefect/flow_runs.py +377 -5
  65. prefect/flows.py +248 -165
  66. prefect/futures.py +186 -345
  67. prefect/infrastructure/__init__.py +0 -27
  68. prefect/infrastructure/provisioners/__init__.py +5 -3
  69. prefect/infrastructure/provisioners/cloud_run.py +11 -6
  70. prefect/infrastructure/provisioners/container_instance.py +11 -7
  71. prefect/infrastructure/provisioners/ecs.py +6 -4
  72. prefect/infrastructure/provisioners/modal.py +8 -5
  73. prefect/input/actions.py +2 -4
  74. prefect/input/run_input.py +5 -7
  75. prefect/logging/formatters.py +0 -2
  76. prefect/logging/handlers.py +3 -11
  77. prefect/logging/loggers.py +2 -2
  78. prefect/manifests.py +2 -1
  79. prefect/records/__init__.py +1 -0
  80. prefect/records/result_store.py +42 -0
  81. prefect/records/store.py +9 -0
  82. prefect/results.py +43 -39
  83. prefect/runner/runner.py +9 -9
  84. prefect/runner/server.py +6 -10
  85. prefect/runner/storage.py +3 -8
  86. prefect/runner/submit.py +2 -2
  87. prefect/runner/utils.py +2 -2
  88. prefect/serializers.py +24 -35
  89. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
  90. prefect/settings.py +70 -133
  91. prefect/states.py +17 -47
  92. prefect/task_engine.py +697 -58
  93. prefect/task_runners.py +269 -301
  94. prefect/task_server.py +53 -34
  95. prefect/tasks.py +327 -337
  96. prefect/transactions.py +220 -0
  97. prefect/types/__init__.py +61 -82
  98. prefect/utilities/asyncutils.py +195 -136
  99. prefect/utilities/callables.py +121 -41
  100. prefect/utilities/collections.py +23 -38
  101. prefect/utilities/dispatch.py +11 -3
  102. prefect/utilities/dockerutils.py +4 -0
  103. prefect/utilities/engine.py +140 -20
  104. prefect/utilities/importtools.py +26 -27
  105. prefect/utilities/pydantic.py +128 -38
  106. prefect/utilities/schema_tools/hydration.py +5 -1
  107. prefect/utilities/templating.py +12 -2
  108. prefect/variables.py +78 -61
  109. prefect/workers/__init__.py +0 -1
  110. prefect/workers/base.py +15 -17
  111. prefect/workers/process.py +3 -8
  112. prefect/workers/server.py +2 -2
  113. {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/METADATA +22 -21
  114. prefect_client-3.0.0rc1.dist-info/RECORD +176 -0
  115. prefect/_internal/pydantic/_base_model.py +0 -51
  116. prefect/_internal/pydantic/_compat.py +0 -82
  117. prefect/_internal/pydantic/_flags.py +0 -20
  118. prefect/_internal/pydantic/_types.py +0 -8
  119. prefect/_internal/pydantic/utilities/__init__.py +0 -0
  120. prefect/_internal/pydantic/utilities/config_dict.py +0 -72
  121. prefect/_internal/pydantic/utilities/field_validator.py +0 -150
  122. prefect/_internal/pydantic/utilities/model_construct.py +0 -56
  123. prefect/_internal/pydantic/utilities/model_copy.py +0 -55
  124. prefect/_internal/pydantic/utilities/model_dump.py +0 -136
  125. prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
  126. prefect/_internal/pydantic/utilities/model_fields.py +0 -50
  127. prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
  128. prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
  129. prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
  130. prefect/_internal/pydantic/utilities/model_validate.py +0 -75
  131. prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
  132. prefect/_internal/pydantic/utilities/model_validator.py +0 -87
  133. prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
  134. prefect/_vendor/__init__.py +0 -0
  135. prefect/_vendor/fastapi/__init__.py +0 -25
  136. prefect/_vendor/fastapi/applications.py +0 -946
  137. prefect/_vendor/fastapi/background.py +0 -3
  138. prefect/_vendor/fastapi/concurrency.py +0 -44
  139. prefect/_vendor/fastapi/datastructures.py +0 -58
  140. prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
  141. prefect/_vendor/fastapi/dependencies/models.py +0 -64
  142. prefect/_vendor/fastapi/dependencies/utils.py +0 -877
  143. prefect/_vendor/fastapi/encoders.py +0 -177
  144. prefect/_vendor/fastapi/exception_handlers.py +0 -40
  145. prefect/_vendor/fastapi/exceptions.py +0 -46
  146. prefect/_vendor/fastapi/logger.py +0 -3
  147. prefect/_vendor/fastapi/middleware/__init__.py +0 -1
  148. prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
  149. prefect/_vendor/fastapi/middleware/cors.py +0 -3
  150. prefect/_vendor/fastapi/middleware/gzip.py +0 -3
  151. prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
  152. prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
  153. prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
  154. prefect/_vendor/fastapi/openapi/__init__.py +0 -0
  155. prefect/_vendor/fastapi/openapi/constants.py +0 -2
  156. prefect/_vendor/fastapi/openapi/docs.py +0 -203
  157. prefect/_vendor/fastapi/openapi/models.py +0 -480
  158. prefect/_vendor/fastapi/openapi/utils.py +0 -485
  159. prefect/_vendor/fastapi/param_functions.py +0 -340
  160. prefect/_vendor/fastapi/params.py +0 -453
  161. prefect/_vendor/fastapi/requests.py +0 -4
  162. prefect/_vendor/fastapi/responses.py +0 -40
  163. prefect/_vendor/fastapi/routing.py +0 -1331
  164. prefect/_vendor/fastapi/security/__init__.py +0 -15
  165. prefect/_vendor/fastapi/security/api_key.py +0 -98
  166. prefect/_vendor/fastapi/security/base.py +0 -6
  167. prefect/_vendor/fastapi/security/http.py +0 -172
  168. prefect/_vendor/fastapi/security/oauth2.py +0 -227
  169. prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
  170. prefect/_vendor/fastapi/security/utils.py +0 -10
  171. prefect/_vendor/fastapi/staticfiles.py +0 -1
  172. prefect/_vendor/fastapi/templating.py +0 -3
  173. prefect/_vendor/fastapi/testclient.py +0 -1
  174. prefect/_vendor/fastapi/types.py +0 -3
  175. prefect/_vendor/fastapi/utils.py +0 -235
  176. prefect/_vendor/fastapi/websockets.py +0 -7
  177. prefect/_vendor/starlette/__init__.py +0 -1
  178. prefect/_vendor/starlette/_compat.py +0 -28
  179. prefect/_vendor/starlette/_exception_handler.py +0 -80
  180. prefect/_vendor/starlette/_utils.py +0 -88
  181. prefect/_vendor/starlette/applications.py +0 -261
  182. prefect/_vendor/starlette/authentication.py +0 -159
  183. prefect/_vendor/starlette/background.py +0 -43
  184. prefect/_vendor/starlette/concurrency.py +0 -59
  185. prefect/_vendor/starlette/config.py +0 -151
  186. prefect/_vendor/starlette/convertors.py +0 -87
  187. prefect/_vendor/starlette/datastructures.py +0 -707
  188. prefect/_vendor/starlette/endpoints.py +0 -130
  189. prefect/_vendor/starlette/exceptions.py +0 -60
  190. prefect/_vendor/starlette/formparsers.py +0 -276
  191. prefect/_vendor/starlette/middleware/__init__.py +0 -17
  192. prefect/_vendor/starlette/middleware/authentication.py +0 -52
  193. prefect/_vendor/starlette/middleware/base.py +0 -220
  194. prefect/_vendor/starlette/middleware/cors.py +0 -176
  195. prefect/_vendor/starlette/middleware/errors.py +0 -265
  196. prefect/_vendor/starlette/middleware/exceptions.py +0 -74
  197. prefect/_vendor/starlette/middleware/gzip.py +0 -113
  198. prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
  199. prefect/_vendor/starlette/middleware/sessions.py +0 -82
  200. prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
  201. prefect/_vendor/starlette/middleware/wsgi.py +0 -147
  202. prefect/_vendor/starlette/requests.py +0 -328
  203. prefect/_vendor/starlette/responses.py +0 -347
  204. prefect/_vendor/starlette/routing.py +0 -933
  205. prefect/_vendor/starlette/schemas.py +0 -154
  206. prefect/_vendor/starlette/staticfiles.py +0 -248
  207. prefect/_vendor/starlette/status.py +0 -199
  208. prefect/_vendor/starlette/templating.py +0 -231
  209. prefect/_vendor/starlette/testclient.py +0 -804
  210. prefect/_vendor/starlette/types.py +0 -30
  211. prefect/_vendor/starlette/websockets.py +0 -193
  212. prefect/agent.py +0 -698
  213. prefect/deployments/deployments.py +0 -1042
  214. prefect/deprecated/__init__.py +0 -0
  215. prefect/deprecated/data_documents.py +0 -350
  216. prefect/deprecated/packaging/__init__.py +0 -12
  217. prefect/deprecated/packaging/base.py +0 -96
  218. prefect/deprecated/packaging/docker.py +0 -146
  219. prefect/deprecated/packaging/file.py +0 -92
  220. prefect/deprecated/packaging/orion.py +0 -80
  221. prefect/deprecated/packaging/serializers.py +0 -171
  222. prefect/events/instrument.py +0 -135
  223. prefect/infrastructure/base.py +0 -323
  224. prefect/infrastructure/container.py +0 -818
  225. prefect/infrastructure/kubernetes.py +0 -920
  226. prefect/infrastructure/process.py +0 -289
  227. prefect/new_task_engine.py +0 -423
  228. prefect/pydantic/__init__.py +0 -76
  229. prefect/pydantic/main.py +0 -39
  230. prefect/software/__init__.py +0 -2
  231. prefect/software/base.py +0 -50
  232. prefect/software/conda.py +0 -199
  233. prefect/software/pip.py +0 -122
  234. prefect/software/python.py +0 -52
  235. prefect/workers/block.py +0 -218
  236. prefect_client-2.19.3.dist-info/RECORD +0 -292
  237. {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/LICENSE +0 -0
  238. {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/WHEEL +0 -0
  239. {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/top_level.txt +0 -0
@@ -6,11 +6,9 @@ format.
6
6
  This will be subject to consolidation and refactoring over the next few months.
7
7
  """
8
8
 
9
- import datetime
10
9
  import json
11
10
  import logging
12
11
  import re
13
- import sys
14
12
  import urllib.parse
15
13
  import warnings
16
14
  from copy import copy
@@ -20,48 +18,24 @@ from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Tuple, Uni
20
18
  import jsonschema
21
19
  import pendulum
22
20
  import yaml
21
+ from pydantic_extra_types.pendulum_dt import DateTime
23
22
 
24
- from prefect._internal.pydantic import HAS_PYDANTIC_V2
25
- from prefect._internal.pydantic._flags import USE_PYDANTIC_V2
26
- from prefect._internal.schemas.fields import DateTimeTZ
27
- from prefect.exceptions import InvalidNameError, InvalidRepositoryURLError
23
+ from prefect.exceptions import InvalidRepositoryURLError
28
24
  from prefect.utilities.annotations import NotSet
25
+ from prefect.utilities.collections import isiterable
29
26
  from prefect.utilities.dockerutils import get_prefect_image_name
30
27
  from prefect.utilities.filesystem import relative_path_to_current_platform
31
28
  from prefect.utilities.importtools import from_qualified_name
32
29
  from prefect.utilities.names import generate_slug
33
30
  from prefect.utilities.pydantic import JsonPatch
34
31
 
35
- BANNED_CHARACTERS = ["/", "%", "&", ">", "<"]
36
32
  LOWERCASE_LETTERS_NUMBERS_AND_DASHES_ONLY_REGEX = "^[a-z0-9-]*$"
37
33
  LOWERCASE_LETTERS_NUMBERS_AND_UNDERSCORES_REGEX = "^[a-z0-9_]*$"
38
34
 
39
35
  if TYPE_CHECKING:
40
36
  from prefect.blocks.core import Block
41
- from prefect.events.schemas import DeploymentTrigger
42
37
  from prefect.utilities.callables import ParameterSchema
43
38
 
44
- if HAS_PYDANTIC_V2:
45
- if USE_PYDANTIC_V2:
46
- # TODO: we need to account for rewriting the validator to not use ModelField
47
- pass
48
- if not USE_PYDANTIC_V2:
49
- from pydantic.v1.fields import ModelField
50
-
51
-
52
- def raise_on_name_with_banned_characters(name: str) -> str:
53
- """
54
- Raise an InvalidNameError if the given name contains any invalid
55
- characters.
56
- """
57
- if name is not None:
58
- if any(c in name for c in BANNED_CHARACTERS):
59
- raise InvalidNameError(
60
- f"Name {name!r} contains an invalid character. "
61
- f"Must not contain any of: {BANNED_CHARACTERS}."
62
- )
63
- return name
64
-
65
39
 
66
40
  def raise_on_name_alphanumeric_dashes_only(
67
41
  value: Optional[str], field_name: str = "value"
@@ -236,6 +210,12 @@ def return_none_schedule(v: Optional[Union[str, dict]]) -> Optional[Union[str, d
236
210
  return v
237
211
 
238
212
 
213
+ def convert_to_strings(value: Union[Any, List[Any]]) -> Union[str, List[str]]:
214
+ if isiterable(value):
215
+ return [str(item) for item in value]
216
+ return str(value)
217
+
218
+
239
219
  ### SCHEDULE SCHEMA VALIDATORS ###
240
220
 
241
221
 
@@ -264,15 +244,15 @@ def reconcile_schedules(cls, values: dict) -> dict:
264
244
  """
265
245
 
266
246
  from prefect.deployments.schedules import (
267
- create_minimal_deployment_schedule,
268
- normalize_to_minimal_deployment_schedules,
247
+ create_deployment_schedule_create,
248
+ normalize_to_deployment_schedule_create,
269
249
  )
270
250
 
271
251
  schedule = values.get("schedule", NotSet)
272
252
  schedules = values.get("schedules", NotSet)
273
253
 
274
254
  if schedules is not NotSet:
275
- values["schedules"] = normalize_to_minimal_deployment_schedules(schedules)
255
+ values["schedules"] = normalize_to_deployment_schedule_create(schedules)
276
256
  elif schedule is not NotSet:
277
257
  values["schedule"] = None
278
258
 
@@ -280,7 +260,7 @@ def reconcile_schedules(cls, values: dict) -> dict:
280
260
  values["schedules"] = []
281
261
  else:
282
262
  values["schedules"] = [
283
- create_minimal_deployment_schedule(
263
+ create_deployment_schedule_create(
284
264
  schedule=schedule, active=values.get("is_schedule_active")
285
265
  )
286
266
  ]
@@ -297,17 +277,17 @@ def reconcile_schedules_runner(values: dict) -> dict:
297
277
  Similar to above, we reconcile the `schedule` and `schedules` fields in a deployment.
298
278
  """
299
279
  from prefect.deployments.schedules import (
300
- create_minimal_deployment_schedule,
301
- normalize_to_minimal_deployment_schedules,
280
+ create_deployment_schedule_create,
281
+ normalize_to_deployment_schedule_create,
302
282
  )
303
283
 
304
284
  schedule = values.get("schedule")
305
285
  schedules = values.get("schedules")
306
286
 
307
287
  if schedules is None and schedule is not None:
308
- values["schedules"] = [create_minimal_deployment_schedule(schedule)]
288
+ values["schedules"] = [create_deployment_schedule_create(schedule)]
309
289
  elif schedules is not None and len(schedules) > 0:
310
- values["schedules"] = normalize_to_minimal_deployment_schedules(schedules)
290
+ values["schedules"] = normalize_to_deployment_schedule_create(schedules)
311
291
 
312
292
  return values
313
293
 
@@ -316,12 +296,15 @@ def set_deployment_schedules(values: dict) -> dict:
316
296
  from prefect.server.schemas.actions import DeploymentScheduleCreate
317
297
 
318
298
  if not values.get("schedules") and values.get("schedule"):
319
- values["schedules"] = [
320
- DeploymentScheduleCreate(
321
- schedule=values["schedule"],
322
- active=values["is_schedule_active"],
323
- )
324
- ]
299
+ kwargs = {
300
+ key: values[key]
301
+ for key in ["schedule", "is_schedule_active"]
302
+ if key in values
303
+ }
304
+ if "is_schedule_active" in kwargs:
305
+ kwargs["active"] = kwargs.pop("is_schedule_active")
306
+
307
+ values["schedules"] = [DeploymentScheduleCreate(**kwargs)]
325
308
 
326
309
  return values
327
310
 
@@ -382,13 +365,11 @@ def reconcile_paused_deployment(values):
382
365
  return values
383
366
 
384
367
 
385
- def default_anchor_date(v: DateTimeTZ) -> DateTimeTZ:
386
- if v is None:
387
- return pendulum.now("UTC")
368
+ def default_anchor_date(v: DateTime) -> DateTime:
388
369
  return pendulum.instance(v)
389
370
 
390
371
 
391
- def get_valid_timezones(v: str) -> Tuple[str, ...]:
372
+ def get_valid_timezones(v: Optional[str]) -> Tuple[str, ...]:
392
373
  # pendulum.tz.timezones is a callable in 3.0 and above
393
374
  # https://github.com/PrefectHQ/prefect/issues/11619
394
375
  if callable(pendulum.tz.timezones):
@@ -397,27 +378,6 @@ def get_valid_timezones(v: str) -> Tuple[str, ...]:
397
378
  return pendulum.tz.timezones
398
379
 
399
380
 
400
- def validate_rrule_timezone(v: str) -> str:
401
- """
402
- Validate that the provided timezone is a valid IANA timezone.
403
-
404
- Unfortunately this list is slightly different from the list of valid
405
- timezones in pendulum that we use for cron and interval timezone validation.
406
- """
407
- from prefect._internal.pytz import HAS_PYTZ
408
-
409
- if HAS_PYTZ:
410
- import pytz
411
- else:
412
- from prefect._internal import pytz
413
-
414
- if v and v not in pytz.all_timezones_set:
415
- raise ValueError(f'Invalid timezone: "{v}"')
416
- elif v is None:
417
- return "UTC"
418
- return v
419
-
420
-
421
381
  def validate_timezone(v: str, timezones: Tuple[str, ...]) -> str:
422
382
  if v and v not in timezones:
423
383
  raise ValueError(
@@ -427,7 +387,8 @@ def validate_timezone(v: str, timezones: Tuple[str, ...]) -> str:
427
387
  return v
428
388
 
429
389
 
430
- def default_timezone(v: str, values: Optional[dict] = {}) -> str:
390
+ def default_timezone(v: Optional[str], values: Optional[dict] = None) -> str:
391
+ values = values or {}
431
392
  timezones = get_valid_timezones(v)
432
393
 
433
394
  if v is not None:
@@ -435,7 +396,7 @@ def default_timezone(v: str, values: Optional[dict] = {}) -> str:
435
396
 
436
397
  # anchor schedules
437
398
  elif v is None and values and values.get("anchor_date"):
438
- tz = values["anchor_date"].tz.name
399
+ tz = getattr(values["anchor_date"].tz, "name", None) or "UTC"
439
400
  if tz in timezones:
440
401
  return tz
441
402
  # sometimes anchor dates have "timezones" that are UTC offsets
@@ -485,34 +446,6 @@ def validate_rrule_string(v: str) -> str:
485
446
  return v
486
447
 
487
448
 
488
- ### AUTOMATION SCHEMA VALIDATORS ###
489
-
490
-
491
- def validate_trigger_within(
492
- value: datetime.timedelta, field: "ModelField"
493
- ) -> datetime.timedelta:
494
- """
495
- Validate that the `within` field is greater than the minimum value.
496
- """
497
- minimum = field.field_info.extra["minimum"]
498
- if value.total_seconds() < minimum:
499
- raise ValueError("The minimum `within` is 0 seconds")
500
- return value
501
-
502
-
503
- def validate_automation_names(
504
- field_value: List["DeploymentTrigger"], values: dict
505
- ) -> List["DeploymentTrigger"]:
506
- """
507
- Ensure that each trigger has a name for its automation if none is provided.
508
- """
509
- for i, trigger in enumerate(field_value, start=1):
510
- if trigger.name is None:
511
- trigger.name = f"{values['name']}__automation_{i}"
512
-
513
- return field_value
514
-
515
-
516
449
  ### INFRASTRUCTURE SCHEMA VALIDATORS ###
517
450
 
518
451
 
@@ -589,7 +522,6 @@ def set_default_image(values: dict) -> dict:
589
522
  """
590
523
  Set the default image for a Kubernetes job if not provided.
591
524
  """
592
- from prefect.utilities.dockerutils import get_prefect_image_name
593
525
 
594
526
  job = values.get("job")
595
527
  image = values.get("image")
@@ -616,23 +548,6 @@ def get_or_create_state_name(v: str, values: dict) -> str:
616
548
  return v
617
549
 
618
550
 
619
- def set_default_scheduled_time(cls, values: dict) -> dict:
620
- """
621
- TODO: This should throw an error instead of setting a default but is out of
622
- scope for https://github.com/PrefectHQ/orion/pull/174/ and can be rolled
623
- into work refactoring state initialization
624
- """
625
- from prefect.server.schemas.states import StateType
626
-
627
- if values.get("type") == StateType.SCHEDULED:
628
- state_details = values.setdefault(
629
- "state_details", cls.__fields__["state_details"].get_default()
630
- )
631
- if not state_details.scheduled_time:
632
- state_details.scheduled_time = pendulum.now("utc")
633
- return values
634
-
635
-
636
551
  def get_or_create_run_name(name):
637
552
  return name or generate_slug(2)
638
553
 
@@ -882,20 +797,6 @@ def check_volume_format(volumes: List[str]) -> List[str]:
882
797
  return volumes
883
798
 
884
799
 
885
- def assign_default_base_image(values: Mapping[str, Any]) -> Mapping[str, Any]:
886
- from prefect.software.conda import CondaEnvironment
887
-
888
- if not values.get("base_image") and not values.get("dockerfile"):
889
- values["base_image"] = get_prefect_image_name(
890
- flavor=(
891
- "conda"
892
- if isinstance(values.get("python_environment"), CondaEnvironment)
893
- else None
894
- )
895
- )
896
- return values
897
-
898
-
899
800
  def base_image_xor_dockerfile(values: Mapping[str, Any]):
900
801
  if values.get("base_image") and values.get("dockerfile"):
901
802
  raise ValueError(
@@ -904,14 +805,6 @@ def base_image_xor_dockerfile(values: Mapping[str, Any]):
904
805
  return values
905
806
 
906
807
 
907
- def set_default_python_environment(values: Mapping[str, Any]) -> Mapping[str, Any]:
908
- from prefect.software.python import PythonEnvironment
909
-
910
- if values.get("base_image") and not values.get("python_environment"):
911
- values["python_environment"] = PythonEnvironment.from_environment()
912
- return values
913
-
914
-
915
808
  ### SETTINGS SCHEMA VALIDATORS ###
916
809
 
917
810
 
@@ -929,7 +822,7 @@ def validate_settings(value: dict) -> dict:
929
822
  elif isinstance(setting, Setting):
930
823
  validated[setting] = val
931
824
  else:
932
- raise ValueError(f"Unknown setting {setting!r}.")
825
+ warnings.warn(f"Setting {setting!r} is not recognized and will be ignored.")
933
826
 
934
827
  return validated
935
828
 
@@ -976,12 +869,6 @@ def set_run_policy_deprecated_fields(values: dict) -> dict:
976
869
  ### PYTHON ENVIRONMENT SCHEMA VALIDATORS ###
977
870
 
978
871
 
979
- def infer_python_version(value: Optional[str]) -> Optional[str]:
980
- if value is None:
981
- return f"{sys.version_info.major}.{sys.version_info.minor}"
982
- return value
983
-
984
-
985
872
  def return_v_or_none(v: Optional[str]) -> Optional[str]:
986
873
  """Make sure that empty strings are treated as None"""
987
874
  if not v:
@@ -989,19 +876,6 @@ def return_v_or_none(v: Optional[str]) -> Optional[str]:
989
876
  return v
990
877
 
991
878
 
992
- ### INFRASTRUCTURE BLOCK SCHEMA VALIDATORS ###
993
-
994
-
995
- def validate_block_is_infrastructure(v: "Block") -> "Block":
996
- from prefect.infrastructure.base import Infrastructure
997
-
998
- print("v: ", v)
999
- if not isinstance(v, Infrastructure):
1000
- raise TypeError("Provided block is not a valid infrastructure block.")
1001
-
1002
- return v
1003
-
1004
-
1005
879
  ### BLOCK SCHEMA VALIDATORS ###
1006
880
 
1007
881
 
@@ -1032,3 +906,31 @@ def validate_command(v: str) -> Path:
1032
906
  if v:
1033
907
  return relative_path_to_current_platform(v)
1034
908
  return v
909
+
910
+
911
+ ### UNCATEGORIZED VALIDATORS ###
912
+
913
+ # the above categories seem to be getting a bit unwieldy, so this is a temporary
914
+ # catch-all for validators until we organize these into files
915
+
916
+
917
+ def validate_block_document_name(value):
918
+ if value is not None:
919
+ raise_on_name_alphanumeric_dashes_only(value, field_name="Block document name")
920
+ return value
921
+
922
+
923
+ def validate_artifact_key(value):
924
+ raise_on_name_alphanumeric_dashes_only(value, field_name="Artifact key")
925
+ return value
926
+
927
+
928
+ def validate_variable_name(value):
929
+ if value is not None:
930
+ raise_on_name_alphanumeric_underscores_only(value, field_name="Variable name")
931
+ return value
932
+
933
+
934
+ def validate_block_type_slug(value):
935
+ raise_on_name_alphanumeric_dashes_only(value, field_name="Block type slug")
936
+ return value
prefect/artifacts.py CHANGED
@@ -6,20 +6,26 @@ from __future__ import annotations
6
6
 
7
7
  import json # noqa: I001
8
8
  import math
9
- from typing import Any, Dict, List, Optional, Tuple, Union
9
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
10
10
  from uuid import UUID
11
11
 
12
- from typing_extensions import Self
13
-
14
- from prefect.client.orchestration import PrefectClient
15
12
  from prefect.client.schemas.actions import ArtifactCreate as ArtifactRequest
13
+ from prefect.client.schemas.actions import ArtifactUpdate
16
14
  from prefect.client.schemas.filters import ArtifactFilter, ArtifactFilterKey
17
- from prefect.client.schemas.objects import Artifact as ArtifactResponse
18
15
  from prefect.client.schemas.sorting import ArtifactSort
19
16
  from prefect.client.utilities import get_or_create_client, inject_client
17
+ from prefect.logging.loggers import get_logger
20
18
  from prefect.utilities.asyncutils import sync_compatible
21
19
  from prefect.utilities.context import get_task_and_flow_run_ids
22
20
 
21
+ logger = get_logger("artifacts")
22
+
23
+ if TYPE_CHECKING:
24
+ from typing_extensions import Self
25
+
26
+ from prefect.client.orchestration import PrefectClient
27
+ from prefect.client.schemas.objects import Artifact as ArtifactResponse
28
+
23
29
 
24
30
  class Artifact(ArtifactRequest):
25
31
  """
@@ -36,9 +42,9 @@ class Artifact(ArtifactRequest):
36
42
 
37
43
  @sync_compatible
38
44
  async def create(
39
- self: Self,
40
- client: Optional[PrefectClient] = None,
41
- ) -> ArtifactResponse:
45
+ self: "Self",
46
+ client: Optional["PrefectClient"] = None,
47
+ ) -> "ArtifactResponse":
42
48
  """
43
49
  A method to create an artifact.
44
50
 
@@ -64,8 +70,8 @@ class Artifact(ArtifactRequest):
64
70
  @classmethod
65
71
  @sync_compatible
66
72
  async def get(
67
- cls, key: Optional[str] = None, client: Optional[PrefectClient] = None
68
- ) -> Optional[ArtifactResponse]:
73
+ cls, key: Optional[str] = None, client: Optional["PrefectClient"] = None
74
+ ) -> Optional["ArtifactResponse"]:
69
75
  """
70
76
  A method to get an artifact.
71
77
 
@@ -95,9 +101,9 @@ class Artifact(ArtifactRequest):
95
101
  key: Optional[str] = None,
96
102
  description: Optional[str] = None,
97
103
  data: Optional[Union[Dict[str, Any], Any]] = None,
98
- client: Optional[PrefectClient] = None,
104
+ client: Optional["PrefectClient"] = None,
99
105
  **kwargs: Any,
100
- ) -> Tuple[ArtifactResponse, bool]:
106
+ ) -> Tuple["ArtifactResponse", bool]:
101
107
  """
102
108
  A method to get or create an artifact.
103
109
 
@@ -171,13 +177,54 @@ class TableArtifact(Artifact):
171
177
  return json.dumps(self._sanitize(self.table))
172
178
 
173
179
 
180
+ class ProgressArtifact(Artifact):
181
+ progress: float
182
+ type: Optional[str] = "progress"
183
+
184
+ async def format(self) -> float:
185
+ # Ensure progress is between 0 and 100
186
+ min_progress = 0.0
187
+ max_progress = 100.0
188
+ if self.progress < min_progress or self.progress > max_progress:
189
+ logger.warning(
190
+ f"ProgressArtifact received an invalid value, Progress: {self.progress}%"
191
+ )
192
+ self.progress = max(min_progress, min(self.progress, max_progress))
193
+ logger.warning(f"Interpreting as {self.progress}% progress")
194
+
195
+ return self.progress
196
+
197
+
198
+ class ImageArtifact(Artifact):
199
+ """
200
+ An artifact that will display an image from a publicly accessible URL in the UI.
201
+
202
+ Arguments:
203
+ image_url: The URL of the image to display.
204
+ """
205
+
206
+ image_url: str
207
+ type: Optional[str] = "image"
208
+
209
+ async def format(self) -> str:
210
+ """
211
+ This method is used to format the artifact data so it can be properly sent
212
+ to the API when the .create() method is called. It is async because the
213
+ method is awaited in the parent class.
214
+
215
+ Returns:
216
+ str: The image URL.
217
+ """
218
+ return self.image_url
219
+
220
+
174
221
  @inject_client
175
222
  async def _create_artifact(
176
223
  type: str,
177
224
  key: Optional[str] = None,
178
225
  description: Optional[str] = None,
179
226
  data: Optional[Union[Dict[str, Any], Any]] = None,
180
- client: Optional[PrefectClient] = None,
227
+ client: Optional["PrefectClient"] = None,
181
228
  ) -> UUID:
182
229
  """
183
230
  Helper function to create an artifact.
@@ -210,7 +257,7 @@ async def create_link_artifact(
210
257
  link_text: Optional[str] = None,
211
258
  key: Optional[str] = None,
212
259
  description: Optional[str] = None,
213
- client: Optional[PrefectClient] = None,
260
+ client: Optional["PrefectClient"] = None,
214
261
  ) -> UUID:
215
262
  """
216
263
  Create a link artifact.
@@ -292,3 +339,103 @@ async def create_table_artifact(
292
339
  ).create()
293
340
 
294
341
  return artifact.id
342
+
343
+
344
+ @sync_compatible
345
+ async def create_progress_artifact(
346
+ progress: float,
347
+ key: Optional[str] = None,
348
+ description: Optional[str] = None,
349
+ ) -> UUID:
350
+ """
351
+ Create a progress artifact.
352
+
353
+ Arguments:
354
+ progress: The percentage of progress represented by a float between 0 and 100.
355
+ key: A user-provided string identifier.
356
+ Required for the artifact to show in the Artifacts page in the UI.
357
+ The key must only contain lowercase letters, numbers, and dashes.
358
+ description: A user-specified description of the artifact.
359
+
360
+ Returns:
361
+ The progress artifact ID.
362
+ """
363
+
364
+ artifact = await ProgressArtifact(
365
+ key=key,
366
+ description=description,
367
+ progress=progress,
368
+ ).create()
369
+
370
+ return artifact.id
371
+
372
+
373
+ @sync_compatible
374
+ async def update_progress_artifact(
375
+ artifact_id: UUID,
376
+ progress: float,
377
+ description: Optional[str] = None,
378
+ client: Optional[PrefectClient] = None,
379
+ ) -> UUID:
380
+ """
381
+ Update a progress artifact.
382
+
383
+ Arguments:
384
+ artifact_id: The ID of the artifact to update.
385
+ progress: The percentage of progress represented by a float between 0 and 100.
386
+ description: A user-specified description of the artifact.
387
+
388
+ Returns:
389
+ The progress artifact ID.
390
+ """
391
+
392
+ client, _ = get_or_create_client(client)
393
+
394
+ artifact = ProgressArtifact(
395
+ description=description,
396
+ progress=progress,
397
+ )
398
+ update = (
399
+ ArtifactUpdate(
400
+ description=artifact.description,
401
+ data=await artifact.format(),
402
+ )
403
+ if description
404
+ else ArtifactUpdate(data=await artifact.format())
405
+ )
406
+
407
+ await client.update_artifact(
408
+ artifact_id=artifact_id,
409
+ artifact=update,
410
+ )
411
+
412
+ return artifact_id
413
+
414
+
415
+ @sync_compatible
416
+ async def create_image_artifact(
417
+ image_url: str,
418
+ key: Optional[str] = None,
419
+ description: Optional[str] = None,
420
+ ) -> UUID:
421
+ """
422
+ Create an image artifact.
423
+
424
+ Arguments:
425
+ image_url: The URL of the image to display.
426
+ key: A user-provided string identifier.
427
+ Required for the artifact to show in the Artifacts page in the UI.
428
+ The key must only contain lowercase letters, numbers, and dashes.
429
+ description: A user-specified description of the artifact.
430
+
431
+ Returns:
432
+ The image artifact ID.
433
+ """
434
+
435
+ artifact = await ImageArtifact(
436
+ key=key,
437
+ description=description,
438
+ image_url=image_url,
439
+ ).create()
440
+
441
+ return artifact.id
prefect/automations.py CHANGED
@@ -5,6 +5,24 @@ from pydantic import Field
5
5
  from typing_extensions import Self
6
6
 
7
7
  from prefect.client.utilities import get_or_create_client
8
+ from prefect.events.actions import (
9
+ CallWebhook,
10
+ CancelFlowRun,
11
+ ChangeFlowRunState,
12
+ DeclareIncident,
13
+ DoNothing,
14
+ PauseAutomation,
15
+ PauseDeployment,
16
+ PauseWorkPool,
17
+ PauseWorkQueue,
18
+ ResumeAutomation,
19
+ ResumeDeployment,
20
+ ResumeWorkPool,
21
+ ResumeWorkQueue,
22
+ RunDeployment,
23
+ SendNotification,
24
+ SuspendFlowRun,
25
+ )
8
26
  from prefect.events.schemas.automations import (
9
27
  AutomationCore,
10
28
  CompositeTrigger,
@@ -37,6 +55,23 @@ __all__ = [
37
55
  "SequenceTrigger",
38
56
  "CompoundTrigger",
39
57
  "MetricTriggerQuery",
58
+ # action types
59
+ "DoNothing",
60
+ "RunDeployment",
61
+ "PauseDeployment",
62
+ "ResumeDeployment",
63
+ "CancelFlowRun",
64
+ "ChangeFlowRunState",
65
+ "PauseWorkQueue",
66
+ "ResumeWorkQueue",
67
+ "SendNotification",
68
+ "CallWebhook",
69
+ "PauseAutomation",
70
+ "ResumeAutomation",
71
+ "SuspendFlowRun",
72
+ "PauseWorkPool",
73
+ "ResumeWorkPool",
74
+ "DeclareIncident",
40
75
  ]
41
76
 
42
77
 
@@ -65,7 +100,7 @@ class Automation(AutomationCore):
65
100
  created_automation = auto_to_create.create()
66
101
  """
67
102
  client, _ = get_or_create_client()
68
- automation = AutomationCore(**self.dict(exclude={"id"}))
103
+ automation = AutomationCore(**self.model_dump(exclude={"id"}))
69
104
  self.id = await client.create_automation(automation=automation)
70
105
  return self
71
106
 
@@ -79,7 +114,7 @@ class Automation(AutomationCore):
79
114
  """
80
115
 
81
116
  client, _ = get_or_create_client()
82
- automation = AutomationCore(**self.dict(exclude={"id", "owner_resource"}))
117
+ automation = AutomationCore(**self.model_dump(exclude={"id", "owner_resource"}))
83
118
  await client.update_automation(automation_id=self.id, automation=automation)
84
119
 
85
120
  @classmethod
@@ -106,11 +141,11 @@ class Automation(AutomationCore):
106
141
  except PrefectHTTPStatusError as exc:
107
142
  if exc.response.status_code == 404:
108
143
  raise ValueError(f"Automation with ID {id!r} not found")
109
- return Automation(**automation.dict())
144
+ return Automation(**automation.model_dump())
110
145
  else:
111
146
  automation = await client.read_automations_by_name(name=name)
112
147
  if len(automation) > 0:
113
- return Automation(**automation[0].dict()) if automation else None
148
+ return Automation(**automation[0].model_dump()) if automation else None
114
149
  else:
115
150
  raise ValueError(f"Automation with name {name!r} not found")
116
151
 
@@ -73,7 +73,7 @@ class NotificationBlock(Block, ABC):
73
73
  """
74
74
 
75
75
  _block_schema_capabilities = ["notify"]
76
- _events_excluded_methods = Block._events_excluded_methods + ["notify"]
76
+ _events_excluded_methods = Block._events_excluded_methods.default + ["notify"]
77
77
 
78
78
  @property
79
79
  def logger(self) -> Logger: