prefect-client 3.1.5__py3-none-any.whl → 3.1.7__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 (114) hide show
  1. prefect/__init__.py +3 -0
  2. prefect/_experimental/__init__.py +0 -0
  3. prefect/_experimental/lineage.py +181 -0
  4. prefect/_internal/compatibility/async_dispatch.py +38 -9
  5. prefect/_internal/compatibility/migration.py +1 -1
  6. prefect/_internal/concurrency/api.py +52 -52
  7. prefect/_internal/concurrency/calls.py +59 -35
  8. prefect/_internal/concurrency/cancellation.py +34 -18
  9. prefect/_internal/concurrency/event_loop.py +7 -6
  10. prefect/_internal/concurrency/threads.py +41 -33
  11. prefect/_internal/concurrency/waiters.py +28 -21
  12. prefect/_internal/pydantic/v1_schema.py +2 -2
  13. prefect/_internal/pydantic/v2_schema.py +10 -9
  14. prefect/_internal/pydantic/v2_validated_func.py +15 -10
  15. prefect/_internal/retries.py +15 -6
  16. prefect/_internal/schemas/bases.py +11 -8
  17. prefect/_internal/schemas/validators.py +7 -5
  18. prefect/_version.py +3 -3
  19. prefect/automations.py +53 -47
  20. prefect/blocks/abstract.py +12 -10
  21. prefect/blocks/core.py +148 -19
  22. prefect/blocks/system.py +2 -1
  23. prefect/cache_policies.py +11 -11
  24. prefect/client/__init__.py +3 -1
  25. prefect/client/base.py +36 -37
  26. prefect/client/cloud.py +26 -19
  27. prefect/client/collections.py +2 -2
  28. prefect/client/orchestration.py +430 -273
  29. prefect/client/schemas/__init__.py +24 -0
  30. prefect/client/schemas/actions.py +128 -121
  31. prefect/client/schemas/filters.py +1 -1
  32. prefect/client/schemas/objects.py +114 -85
  33. prefect/client/schemas/responses.py +19 -20
  34. prefect/client/schemas/schedules.py +136 -93
  35. prefect/client/subscriptions.py +30 -15
  36. prefect/client/utilities.py +46 -36
  37. prefect/concurrency/asyncio.py +6 -9
  38. prefect/concurrency/sync.py +35 -5
  39. prefect/context.py +40 -32
  40. prefect/deployments/flow_runs.py +6 -8
  41. prefect/deployments/runner.py +14 -14
  42. prefect/deployments/steps/core.py +3 -1
  43. prefect/deployments/steps/pull.py +60 -12
  44. prefect/docker/__init__.py +1 -1
  45. prefect/events/clients.py +55 -4
  46. prefect/events/filters.py +1 -1
  47. prefect/events/related.py +2 -1
  48. prefect/events/schemas/events.py +26 -21
  49. prefect/events/utilities.py +3 -2
  50. prefect/events/worker.py +8 -0
  51. prefect/filesystems.py +3 -3
  52. prefect/flow_engine.py +87 -87
  53. prefect/flow_runs.py +7 -5
  54. prefect/flows.py +218 -176
  55. prefect/logging/configuration.py +1 -1
  56. prefect/logging/highlighters.py +1 -2
  57. prefect/logging/loggers.py +30 -20
  58. prefect/main.py +17 -24
  59. prefect/results.py +43 -22
  60. prefect/runner/runner.py +43 -21
  61. prefect/runner/server.py +30 -32
  62. prefect/runner/storage.py +3 -3
  63. prefect/runner/submit.py +3 -6
  64. prefect/runner/utils.py +6 -6
  65. prefect/runtime/flow_run.py +7 -0
  66. prefect/serializers.py +28 -24
  67. prefect/settings/constants.py +2 -2
  68. prefect/settings/legacy.py +1 -1
  69. prefect/settings/models/experiments.py +5 -0
  70. prefect/settings/models/server/events.py +10 -0
  71. prefect/task_engine.py +87 -26
  72. prefect/task_runners.py +2 -2
  73. prefect/task_worker.py +43 -25
  74. prefect/tasks.py +148 -142
  75. prefect/telemetry/bootstrap.py +15 -2
  76. prefect/telemetry/instrumentation.py +1 -1
  77. prefect/telemetry/processors.py +10 -7
  78. prefect/telemetry/run_telemetry.py +231 -0
  79. prefect/transactions.py +14 -14
  80. prefect/types/__init__.py +5 -5
  81. prefect/utilities/_engine.py +96 -0
  82. prefect/utilities/annotations.py +25 -18
  83. prefect/utilities/asyncutils.py +126 -140
  84. prefect/utilities/callables.py +87 -78
  85. prefect/utilities/collections.py +278 -117
  86. prefect/utilities/compat.py +13 -21
  87. prefect/utilities/context.py +6 -5
  88. prefect/utilities/dispatch.py +23 -12
  89. prefect/utilities/dockerutils.py +33 -32
  90. prefect/utilities/engine.py +126 -239
  91. prefect/utilities/filesystem.py +18 -15
  92. prefect/utilities/hashing.py +10 -11
  93. prefect/utilities/importtools.py +40 -27
  94. prefect/utilities/math.py +9 -5
  95. prefect/utilities/names.py +3 -3
  96. prefect/utilities/processutils.py +121 -57
  97. prefect/utilities/pydantic.py +41 -36
  98. prefect/utilities/render_swagger.py +22 -12
  99. prefect/utilities/schema_tools/__init__.py +2 -1
  100. prefect/utilities/schema_tools/hydration.py +50 -43
  101. prefect/utilities/schema_tools/validation.py +52 -42
  102. prefect/utilities/services.py +13 -12
  103. prefect/utilities/templating.py +45 -45
  104. prefect/utilities/text.py +2 -1
  105. prefect/utilities/timeout.py +4 -4
  106. prefect/utilities/urls.py +9 -4
  107. prefect/utilities/visualization.py +46 -24
  108. prefect/variables.py +136 -27
  109. prefect/workers/base.py +15 -8
  110. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/METADATA +5 -2
  111. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/RECORD +114 -110
  112. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/LICENSE +0 -0
  113. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/WHEEL +0 -0
  114. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/top_level.txt +0 -0
@@ -13,13 +13,14 @@ import warnings
13
13
  from copy import copy
14
14
  from pathlib import Path
15
15
  from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Tuple, Union
16
+ from uuid import UUID
16
17
 
17
18
  import jsonschema
18
19
  import pendulum
19
20
  import yaml
20
- from pydantic_extra_types.pendulum_dt import DateTime
21
21
 
22
22
  from prefect.exceptions import InvalidRepositoryURLError
23
+ from prefect.types import DateTime
23
24
  from prefect.utilities.collections import isiterable
24
25
  from prefect.utilities.dockerutils import get_prefect_image_name
25
26
  from prefect.utilities.filesystem import relative_path_to_current_platform
@@ -32,6 +33,7 @@ LOWERCASE_LETTERS_NUMBERS_AND_UNDERSCORES_REGEX = "^[a-z0-9_]*$"
32
33
 
33
34
  if TYPE_CHECKING:
34
35
  from prefect.blocks.core import Block
36
+ from prefect.serializers import Serializer
35
37
  from prefect.utilities.callables import ParameterSchema
36
38
 
37
39
 
@@ -577,7 +579,7 @@ def validate_picklelib_and_modules(values: dict) -> dict:
577
579
  return values
578
580
 
579
581
 
580
- def validate_dump_kwargs(value: dict) -> dict:
582
+ def validate_dump_kwargs(value: dict[str, Any]) -> dict[str, Any]:
581
583
  # `default` is set by `object_encoder`. A user provided callable would make this
582
584
  # class unserializable anyway.
583
585
  if "default" in value:
@@ -585,7 +587,7 @@ def validate_dump_kwargs(value: dict) -> dict:
585
587
  return value
586
588
 
587
589
 
588
- def validate_load_kwargs(value: dict) -> dict:
590
+ def validate_load_kwargs(value: dict[str, Any]) -> dict[str, Any]:
589
591
  # `object_hook` is set by `object_decoder`. A user provided callable would make
590
592
  # this class unserializable anyway.
591
593
  if "object_hook" in value:
@@ -595,7 +597,7 @@ def validate_load_kwargs(value: dict) -> dict:
595
597
  return value
596
598
 
597
599
 
598
- def cast_type_names_to_serializers(value):
600
+ def cast_type_names_to_serializers(value: Union[str, "Serializer"]) -> "Serializer":
599
601
  from prefect.serializers import Serializer
600
602
 
601
603
  if isinstance(value, str):
@@ -653,7 +655,7 @@ def validate_message_template_variables(v: Optional[str]) -> Optional[str]:
653
655
  return v
654
656
 
655
657
 
656
- def validate_default_queue_id_not_none(v: Optional[str]) -> Optional[str]:
658
+ def validate_default_queue_id_not_none(v: Optional[UUID]) -> UUID:
657
659
  if v is None:
658
660
  raise ValueError(
659
661
  "`default_queue_id` is a required field. If you are "
prefect/_version.py CHANGED
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2024-12-02T18:57:11-0600",
11
+ "date": "2024-12-16T10:06:12-0800",
12
12
  "dirty": true,
13
13
  "error": null,
14
- "full-revisionid": "3c06654ec8be4f9e9bf5c304814f163b7727d28e",
15
- "version": "3.1.5"
14
+ "full-revisionid": "c05ffa6d1816f980d574b42119dffb9f8c8300a3",
15
+ "version": "3.1.7"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
prefect/automations.py CHANGED
@@ -1,10 +1,10 @@
1
- from typing import Optional
1
+ from typing import Optional, Type
2
2
  from uuid import UUID
3
3
 
4
4
  from pydantic import Field
5
5
  from typing_extensions import Self
6
6
 
7
- from prefect.client.utilities import get_or_create_client
7
+ from prefect.client.orchestration import get_client
8
8
  from prefect.events.actions import (
9
9
  CallWebhook,
10
10
  CancelFlowRun,
@@ -99,10 +99,10 @@ class Automation(AutomationCore):
99
99
  )
100
100
  created_automation = auto_to_create.create()
101
101
  """
102
- client, _ = get_or_create_client()
103
- automation = AutomationCore(**self.model_dump(exclude={"id"}))
104
- self.id = await client.create_automation(automation=automation)
105
- return self
102
+ async with get_client() as client:
103
+ automation = AutomationCore(**self.model_dump(exclude={"id"}))
104
+ self.id = await client.create_automation(automation=automation)
105
+ return self
106
106
 
107
107
  @sync_compatible
108
108
  async def update(self: Self):
@@ -112,15 +112,16 @@ class Automation(AutomationCore):
112
112
  auto.name = "new name"
113
113
  auto.update()
114
114
  """
115
-
116
- client, _ = get_or_create_client()
117
- automation = AutomationCore(**self.model_dump(exclude={"id", "owner_resource"}))
118
- await client.update_automation(automation_id=self.id, automation=automation)
115
+ async with get_client() as client:
116
+ automation = AutomationCore(
117
+ **self.model_dump(exclude={"id", "owner_resource"})
118
+ )
119
+ await client.update_automation(automation_id=self.id, automation=automation)
119
120
 
120
121
  @classmethod
121
122
  @sync_compatible
122
123
  async def read(
123
- cls: Self, id: Optional[UUID] = None, name: Optional[str] = None
124
+ cls: Type[Self], id: Optional[UUID] = None, name: Optional[str] = None
124
125
  ) -> Self:
125
126
  """
126
127
  Read an automation by ID or name.
@@ -134,20 +135,25 @@ class Automation(AutomationCore):
134
135
  raise ValueError("Only one of id or name can be provided")
135
136
  if not id and not name:
136
137
  raise ValueError("One of id or name must be provided")
137
- client, _ = get_or_create_client()
138
- if id:
139
- try:
140
- automation = await client.read_automation(automation_id=id)
141
- except PrefectHTTPStatusError as exc:
142
- if exc.response.status_code == 404:
138
+ async with get_client() as client:
139
+ if id:
140
+ try:
141
+ automation = await client.read_automation(automation_id=id)
142
+ except PrefectHTTPStatusError as exc:
143
+ if exc.response.status_code == 404:
144
+ raise ValueError(f"Automation with ID {id!r} not found")
145
+ raise
146
+ if automation is None:
143
147
  raise ValueError(f"Automation with ID {id!r} not found")
144
- return Automation(**automation.model_dump())
145
- else:
146
- automation = await client.read_automations_by_name(name=name)
147
- if len(automation) > 0:
148
- return Automation(**automation[0].model_dump()) if automation else None
148
+ return Automation(**automation.model_dump())
149
149
  else:
150
- raise ValueError(f"Automation with name {name!r} not found")
150
+ automation = await client.read_automations_by_name(name=name)
151
+ if len(automation) > 0:
152
+ return (
153
+ Automation(**automation[0].model_dump()) if automation else None
154
+ )
155
+ else:
156
+ raise ValueError(f"Automation with name {name!r} not found")
151
157
 
152
158
  @sync_compatible
153
159
  async def delete(self: Self) -> bool:
@@ -155,14 +161,14 @@ class Automation(AutomationCore):
155
161
  auto = Automation.read(id = 123)
156
162
  auto.delete()
157
163
  """
158
- try:
159
- client, _ = get_or_create_client()
160
- await client.delete_automation(self.id)
161
- return True
162
- except PrefectHTTPStatusError as exc:
163
- if exc.response.status_code == 404:
164
- return False
165
- raise
164
+ async with get_client() as client:
165
+ try:
166
+ await client.delete_automation(self.id)
167
+ return True
168
+ except PrefectHTTPStatusError as exc:
169
+ if exc.response.status_code == 404:
170
+ return False
171
+ raise
166
172
 
167
173
  @sync_compatible
168
174
  async def disable(self: Self) -> bool:
@@ -171,14 +177,14 @@ class Automation(AutomationCore):
171
177
  auto = Automation.read(id = 123)
172
178
  auto.disable()
173
179
  """
174
- try:
175
- client, _ = get_or_create_client()
176
- await client.pause_automation(self.id)
177
- return True
178
- except PrefectHTTPStatusError as exc:
179
- if exc.response.status_code == 404:
180
- return False
181
- raise
180
+ async with get_client() as client:
181
+ try:
182
+ await client.pause_automation(self.id)
183
+ return True
184
+ except PrefectHTTPStatusError as exc:
185
+ if exc.response.status_code == 404:
186
+ return False
187
+ raise
182
188
 
183
189
  @sync_compatible
184
190
  async def enable(self: Self) -> bool:
@@ -187,11 +193,11 @@ class Automation(AutomationCore):
187
193
  auto = Automation.read(id = 123)
188
194
  auto.enable()
189
195
  """
190
- try:
191
- client, _ = get_or_create_client()
192
- await client.resume_automation("asd")
193
- return True
194
- except PrefectHTTPStatusError as exc:
195
- if exc.response.status_code == 404:
196
- return False
197
- raise
196
+ async with get_client() as client:
197
+ try:
198
+ await client.resume_automation(self.id)
199
+ return True
200
+ except PrefectHTTPStatusError as exc:
201
+ if exc.response.status_code == 404:
202
+ return False
203
+ raise
@@ -1,6 +1,6 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from contextlib import contextmanager
3
- from logging import Logger
3
+ from logging import Logger, LoggerAdapter
4
4
  from pathlib import Path
5
5
  from typing import (
6
6
  Any,
@@ -15,7 +15,7 @@ from typing import (
15
15
  Union,
16
16
  )
17
17
 
18
- from typing_extensions import Self
18
+ from typing_extensions import Self, TypeAlias
19
19
 
20
20
  from prefect.blocks.core import Block
21
21
  from prefect.exceptions import MissingContextError
@@ -23,6 +23,8 @@ from prefect.logging.loggers import get_logger, get_run_logger
23
23
 
24
24
  T = TypeVar("T")
25
25
 
26
+ LoggerOrAdapter: TypeAlias = Union[Logger, LoggerAdapter]
27
+
26
28
 
27
29
  class CredentialsBlock(Block, ABC):
28
30
  """
@@ -34,7 +36,7 @@ class CredentialsBlock(Block, ABC):
34
36
  """
35
37
 
36
38
  @property
37
- def logger(self) -> Logger:
39
+ def logger(self) -> LoggerOrAdapter:
38
40
  """
39
41
  Returns a logger based on whether the CredentialsBlock
40
42
  is called from within a flow or task run context.
@@ -73,10 +75,10 @@ class NotificationBlock(Block, ABC):
73
75
  """
74
76
 
75
77
  _block_schema_capabilities = ["notify"]
76
- _events_excluded_methods = Block._events_excluded_methods.default + ["notify"]
78
+ _events_excluded_methods = Block._events_excluded_methods + ["notify"]
77
79
 
78
80
  @property
79
- def logger(self) -> Logger:
81
+ def logger(self) -> LoggerOrAdapter:
80
82
  """
81
83
  Returns a logger based on whether the NotificationBlock
82
84
  is called from within a flow or task run context.
@@ -123,7 +125,7 @@ class JobRun(ABC, Generic[T]): # not a block
123
125
  """
124
126
 
125
127
  @property
126
- def logger(self) -> Logger:
128
+ def logger(self) -> LoggerOrAdapter:
127
129
  """
128
130
  Returns a logger based on whether the JobRun
129
131
  is called from within a flow or task run context.
@@ -158,7 +160,7 @@ class JobBlock(Block, ABC):
158
160
  """
159
161
 
160
162
  @property
161
- def logger(self) -> Logger:
163
+ def logger(self) -> LoggerOrAdapter:
162
164
  """
163
165
  Returns a logger based on whether the JobBlock
164
166
  is called from within a flow or task run context.
@@ -202,7 +204,7 @@ class DatabaseBlock(Block, ABC):
202
204
  """
203
205
 
204
206
  @property
205
- def logger(self) -> Logger:
207
+ def logger(self) -> LoggerOrAdapter:
206
208
  """
207
209
  Returns a logger based on whether the DatabaseBlock
208
210
  is called from within a flow or task run context.
@@ -337,7 +339,7 @@ class ObjectStorageBlock(Block, ABC):
337
339
  """
338
340
 
339
341
  @property
340
- def logger(self) -> Logger:
342
+ def logger(self) -> LoggerOrAdapter:
341
343
  """
342
344
  Returns a logger based on whether the ObjectStorageBlock
343
345
  is called from within a flow or task run context.
@@ -469,7 +471,7 @@ class SecretBlock(Block, ABC):
469
471
  """
470
472
 
471
473
  @property
472
- def logger(self) -> Logger:
474
+ def logger(self) -> LoggerOrAdapter:
473
475
  """
474
476
  Returns a logger based on whether the SecretBlock
475
477
  is called from within a flow or task run context.
prefect/blocks/core.py CHANGED
@@ -41,6 +41,7 @@ from pydantic.json_schema import GenerateJsonSchema
41
41
  from typing_extensions import Literal, ParamSpec, Self, get_args
42
42
 
43
43
  import prefect.exceptions
44
+ from prefect._internal.compatibility.async_dispatch import async_dispatch
44
45
  from prefect.client.schemas import (
45
46
  DEFAULT_BLOCK_SCHEMA_VERSION,
46
47
  BlockDocument,
@@ -53,7 +54,7 @@ from prefect.events import emit_event
53
54
  from prefect.logging.loggers import disable_logger
54
55
  from prefect.plugins import load_prefect_collections
55
56
  from prefect.types import SecretDict
56
- from prefect.utilities.asyncutils import sync_compatible
57
+ from prefect.utilities.asyncutils import run_coro_as_sync, sync_compatible
57
58
  from prefect.utilities.collections import listrepr, remove_nested_keys, visit_collection
58
59
  from prefect.utilities.dispatch import lookup_type, register_base_type
59
60
  from prefect.utilities.hashing import hash_objects
@@ -64,7 +65,7 @@ from prefect.utilities.slugify import slugify
64
65
  if TYPE_CHECKING:
65
66
  from pydantic.main import IncEx
66
67
 
67
- from prefect.client.orchestration import PrefectClient
68
+ from prefect.client.orchestration import PrefectClient, SyncPrefectClient
68
69
 
69
70
  R = TypeVar("R")
70
71
  P = ParamSpec("P")
@@ -280,7 +281,7 @@ class Block(BaseModel, ABC):
280
281
  json_schema_extra=schema_extra,
281
282
  )
282
283
 
283
- def __init__(self, *args, **kwargs):
284
+ def __init__(self, *args: Any, **kwargs: Any):
284
285
  super().__init__(*args, **kwargs)
285
286
  self.block_initialization()
286
287
 
@@ -326,7 +327,9 @@ class Block(BaseModel, ABC):
326
327
 
327
328
  # Exclude `save` as it uses the `sync_compatible` decorator and needs to be
328
329
  # decorated directly.
329
- _events_excluded_methods = ["block_initialization", "save", "dict"]
330
+ _events_excluded_methods: ClassVar[List[str]] = PrivateAttr(
331
+ default=["block_initialization", "save", "dict"]
332
+ )
330
333
 
331
334
  @classmethod
332
335
  def __dispatch_key__(cls):
@@ -626,7 +629,8 @@ class Block(BaseModel, ABC):
626
629
  """Generates a default code example for the current class"""
627
630
  qualified_name = to_qualified_name(cls)
628
631
  module_str = ".".join(qualified_name.split(".")[:-1])
629
- class_name = cls.__name__
632
+ origin = cls.__pydantic_generic_metadata__.get("origin") or cls
633
+ class_name = origin.__name__
630
634
  block_variable_name = f'{cls.get_block_type_slug().replace("-", "_")}_block'
631
635
 
632
636
  return dedent(
@@ -775,12 +779,11 @@ class Block(BaseModel, ABC):
775
779
  )
776
780
 
777
781
  @classmethod
778
- @inject_client
779
- async def _get_block_document(
782
+ async def _aget_block_document(
780
783
  cls,
781
784
  name: str,
782
- client: Optional["PrefectClient"] = None,
783
- ):
785
+ client: "PrefectClient",
786
+ ) -> tuple[BlockDocument, str]:
784
787
  if cls.__name__ == "Block":
785
788
  block_type_slug, block_document_name = name.split("/", 1)
786
789
  else:
@@ -799,6 +802,30 @@ class Block(BaseModel, ABC):
799
802
 
800
803
  return block_document, block_document_name
801
804
 
805
+ @classmethod
806
+ def _get_block_document(
807
+ cls,
808
+ name: str,
809
+ client: "SyncPrefectClient",
810
+ ) -> tuple[BlockDocument, str]:
811
+ if cls.__name__ == "Block":
812
+ block_type_slug, block_document_name = name.split("/", 1)
813
+ else:
814
+ block_type_slug = cls.get_block_type_slug()
815
+ block_document_name = name
816
+
817
+ try:
818
+ block_document = client.read_block_document_by_name(
819
+ name=block_document_name, block_type_slug=block_type_slug
820
+ )
821
+ except prefect.exceptions.ObjectNotFound as e:
822
+ raise ValueError(
823
+ f"Unable to find block document named {block_document_name} for block"
824
+ f" type {block_type_slug}"
825
+ ) from e
826
+
827
+ return block_document, block_document_name
828
+
802
829
  @classmethod
803
830
  @sync_compatible
804
831
  @inject_client
@@ -827,9 +854,97 @@ class Block(BaseModel, ABC):
827
854
  return block_document, block_document.name
828
855
 
829
856
  @classmethod
830
- @sync_compatible
831
857
  @inject_client
832
- async def load(
858
+ async def aload(
859
+ cls,
860
+ name: str,
861
+ validate: bool = True,
862
+ client: Optional["PrefectClient"] = None,
863
+ ) -> "Self":
864
+ """
865
+ Retrieves data from the block document with the given name for the block type
866
+ that corresponds with the current class and returns an instantiated version of
867
+ the current class with the data stored in the block document.
868
+
869
+ If a block document for a given block type is saved with a different schema
870
+ than the current class calling `aload`, a warning will be raised.
871
+
872
+ If the current class schema is a subset of the block document schema, the block
873
+ can be loaded as normal using the default `validate = True`.
874
+
875
+ If the current class schema is a superset of the block document schema, `aload`
876
+ must be called with `validate` set to False to prevent a validation error. In
877
+ this case, the block attributes will default to `None` and must be set manually
878
+ and saved to a new block document before the block can be used as expected.
879
+
880
+ Args:
881
+ name: The name or slug of the block document. A block document slug is a
882
+ string with the format <block_type_slug>/<block_document_name>
883
+ validate: If False, the block document will be loaded without Pydantic
884
+ validating the block schema. This is useful if the block schema has
885
+ changed client-side since the block document referred to by `name` was saved.
886
+ client: The client to use to load the block document. If not provided, the
887
+ default client will be injected.
888
+
889
+ Raises:
890
+ ValueError: If the requested block document is not found.
891
+
892
+ Returns:
893
+ An instance of the current class hydrated with the data stored in the
894
+ block document with the specified name.
895
+
896
+ Examples:
897
+ Load from a Block subclass with a block document name:
898
+ ```python
899
+ class Custom(Block):
900
+ message: str
901
+
902
+ Custom(message="Hello!").save("my-custom-message")
903
+
904
+ loaded_block = await Custom.aload("my-custom-message")
905
+ ```
906
+
907
+ Load from Block with a block document slug:
908
+ ```python
909
+ class Custom(Block):
910
+ message: str
911
+
912
+ Custom(message="Hello!").save("my-custom-message")
913
+
914
+ loaded_block = await Block.aload("custom/my-custom-message")
915
+ ```
916
+
917
+ Migrate a block document to a new schema:
918
+ ```python
919
+ # original class
920
+ class Custom(Block):
921
+ message: str
922
+
923
+ Custom(message="Hello!").save("my-custom-message")
924
+
925
+ # Updated class with new required field
926
+ class Custom(Block):
927
+ message: str
928
+ number_of_ducks: int
929
+
930
+ loaded_block = await Custom.aload("my-custom-message", validate=False)
931
+
932
+ # Prints UserWarning about schema mismatch
933
+
934
+ loaded_block.number_of_ducks = 42
935
+
936
+ loaded_block.save("my-custom-message", overwrite=True)
937
+ ```
938
+ """
939
+ if TYPE_CHECKING:
940
+ assert isinstance(client, PrefectClient)
941
+ block_document, _ = await cls._aget_block_document(name, client=client)
942
+
943
+ return cls._load_from_block_document(block_document, validate=validate)
944
+
945
+ @classmethod
946
+ @async_dispatch(aload)
947
+ def load(
833
948
  cls,
834
949
  name: str,
835
950
  validate: bool = True,
@@ -910,9 +1025,19 @@ class Block(BaseModel, ABC):
910
1025
  loaded_block.save("my-custom-message", overwrite=True)
911
1026
  ```
912
1027
  """
913
- block_document, block_document_name = await cls._get_block_document(
914
- name, client=client
915
- )
1028
+ # Need to use a `PrefectClient` here to ensure `Block.load` and `Block.aload` signatures match
1029
+ # TODO: replace with only sync client once all internal calls are updated to use `Block.aload` and `@async_dispatch` is removed
1030
+ if client is None:
1031
+ # If a client wasn't provided, we get to use a sync client
1032
+ from prefect.client.orchestration import get_client
1033
+
1034
+ with get_client(sync_client=True) as sync_client:
1035
+ block_document, _ = cls._get_block_document(name, client=sync_client)
1036
+ else:
1037
+ # If a client was provided, reuse it, even though it's async, to avoid excessive client creation
1038
+ block_document, _ = run_coro_as_sync(
1039
+ cls._aget_block_document(name, client=client)
1040
+ )
916
1041
 
917
1042
  return cls._load_from_block_document(block_document, validate=validate)
918
1043
 
@@ -966,14 +1091,16 @@ class Block(BaseModel, ABC):
966
1091
  """
967
1092
  block_document = None
968
1093
  if isinstance(ref, (str, UUID)):
969
- block_document, _ = await cls._get_block_document_by_id(ref)
1094
+ block_document, _ = await cls._get_block_document_by_id(ref, client=client)
970
1095
  elif isinstance(ref, dict):
971
1096
  if block_document_id := ref.get("block_document_id"):
972
1097
  block_document, _ = await cls._get_block_document_by_id(
973
- block_document_id
1098
+ block_document_id, client=client
974
1099
  )
975
1100
  elif block_document_slug := ref.get("block_document_slug"):
976
- block_document, _ = await cls._get_block_document(block_document_slug)
1101
+ block_document, _ = await cls._get_block_document(
1102
+ block_document_slug, client=client
1103
+ )
977
1104
 
978
1105
  if not block_document:
979
1106
  raise ValueError(f"Invalid reference format {ref!r}.")
@@ -1218,7 +1345,9 @@ class Block(BaseModel, ABC):
1218
1345
  name: str,
1219
1346
  client: Optional["PrefectClient"] = None,
1220
1347
  ):
1221
- block_document, block_document_name = await cls._get_block_document(name)
1348
+ if TYPE_CHECKING:
1349
+ assert isinstance(client, PrefectClient)
1350
+ block_document, _ = await cls._aget_block_document(name, client=client)
1222
1351
 
1223
1352
  await client.delete_block_document(block_document.id)
1224
1353
 
@@ -1229,7 +1358,7 @@ class Block(BaseModel, ABC):
1229
1358
  """
1230
1359
  block_type_slug = kwargs.pop("block_type_slug", None)
1231
1360
  if block_type_slug:
1232
- subcls = lookup_type(cls, dispatch_key=block_type_slug)
1361
+ subcls = cls.get_block_class_from_key(block_type_slug)
1233
1362
  return super().__new__(subcls)
1234
1363
  else:
1235
1364
  return super().__new__(cls)
prefect/blocks/system.py CHANGED
@@ -9,10 +9,10 @@ from pydantic import (
9
9
  field_validator,
10
10
  )
11
11
  from pydantic import Secret as PydanticSecret
12
- from pydantic_extra_types.pendulum_dt import DateTime as PydanticDateTime
13
12
 
14
13
  from prefect._internal.compatibility.deprecated import deprecated_class
15
14
  from prefect.blocks.core import Block
15
+ from prefect.types import DateTime as PydanticDateTime
16
16
 
17
17
  _SecretValueType = Union[
18
18
  Annotated[StrictStr, Field(title="string")],
@@ -130,6 +130,7 @@ class Secret(Block, Generic[T]):
130
130
 
131
131
  _logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/c6f20e556dd16effda9df16551feecfb5822092b-48x48.png"
132
132
  _documentation_url = "https://docs.prefect.io/latest/develop/blocks"
133
+ _description = "A block that represents a secret value. The value stored in this block will be obfuscated when this block is viewed or edited in the UI."
133
134
 
134
135
  value: Union[SecretStr, PydanticSecret[T]] = Field(
135
136
  default=...,