prefect-client 2.20.2__py3-none-any.whl → 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. prefect/__init__.py +74 -110
  2. prefect/_internal/compatibility/deprecated.py +6 -115
  3. prefect/_internal/compatibility/experimental.py +4 -79
  4. prefect/_internal/compatibility/migration.py +166 -0
  5. prefect/_internal/concurrency/__init__.py +2 -2
  6. prefect/_internal/concurrency/api.py +1 -35
  7. prefect/_internal/concurrency/calls.py +0 -6
  8. prefect/_internal/concurrency/cancellation.py +0 -3
  9. prefect/_internal/concurrency/event_loop.py +0 -20
  10. prefect/_internal/concurrency/inspection.py +3 -3
  11. prefect/_internal/concurrency/primitives.py +1 -0
  12. prefect/_internal/concurrency/services.py +23 -0
  13. prefect/_internal/concurrency/threads.py +35 -0
  14. prefect/_internal/concurrency/waiters.py +0 -28
  15. prefect/_internal/integrations.py +7 -0
  16. prefect/_internal/pydantic/__init__.py +0 -45
  17. prefect/_internal/pydantic/annotations/pendulum.py +2 -2
  18. prefect/_internal/pydantic/v1_schema.py +21 -22
  19. prefect/_internal/pydantic/v2_schema.py +0 -2
  20. prefect/_internal/pydantic/v2_validated_func.py +18 -23
  21. prefect/_internal/pytz.py +1 -1
  22. prefect/_internal/retries.py +61 -0
  23. prefect/_internal/schemas/bases.py +45 -177
  24. prefect/_internal/schemas/fields.py +1 -43
  25. prefect/_internal/schemas/validators.py +47 -233
  26. prefect/agent.py +3 -695
  27. prefect/artifacts.py +173 -14
  28. prefect/automations.py +39 -4
  29. prefect/blocks/abstract.py +1 -1
  30. prefect/blocks/core.py +423 -164
  31. prefect/blocks/fields.py +2 -57
  32. prefect/blocks/notifications.py +43 -28
  33. prefect/blocks/redis.py +168 -0
  34. prefect/blocks/system.py +67 -20
  35. prefect/blocks/webhook.py +2 -9
  36. prefect/cache_policies.py +239 -0
  37. prefect/client/__init__.py +4 -0
  38. prefect/client/base.py +33 -27
  39. prefect/client/cloud.py +65 -20
  40. prefect/client/collections.py +1 -1
  41. prefect/client/orchestration.py +667 -440
  42. prefect/client/schemas/actions.py +115 -100
  43. prefect/client/schemas/filters.py +46 -52
  44. prefect/client/schemas/objects.py +228 -178
  45. prefect/client/schemas/responses.py +18 -36
  46. prefect/client/schemas/schedules.py +55 -36
  47. prefect/client/schemas/sorting.py +2 -0
  48. prefect/client/subscriptions.py +8 -7
  49. prefect/client/types/flexible_schedule_list.py +11 -0
  50. prefect/client/utilities.py +9 -6
  51. prefect/concurrency/asyncio.py +60 -11
  52. prefect/concurrency/context.py +24 -0
  53. prefect/concurrency/events.py +2 -2
  54. prefect/concurrency/services.py +46 -16
  55. prefect/concurrency/sync.py +51 -7
  56. prefect/concurrency/v1/asyncio.py +143 -0
  57. prefect/concurrency/v1/context.py +27 -0
  58. prefect/concurrency/v1/events.py +61 -0
  59. prefect/concurrency/v1/services.py +116 -0
  60. prefect/concurrency/v1/sync.py +92 -0
  61. prefect/context.py +246 -149
  62. prefect/deployments/__init__.py +33 -18
  63. prefect/deployments/base.py +10 -15
  64. prefect/deployments/deployments.py +2 -1048
  65. prefect/deployments/flow_runs.py +178 -0
  66. prefect/deployments/runner.py +72 -173
  67. prefect/deployments/schedules.py +31 -25
  68. prefect/deployments/steps/__init__.py +0 -1
  69. prefect/deployments/steps/core.py +7 -0
  70. prefect/deployments/steps/pull.py +15 -21
  71. prefect/deployments/steps/utility.py +2 -1
  72. prefect/docker/__init__.py +20 -0
  73. prefect/docker/docker_image.py +82 -0
  74. prefect/engine.py +15 -2466
  75. prefect/events/actions.py +17 -23
  76. prefect/events/cli/automations.py +20 -7
  77. prefect/events/clients.py +142 -80
  78. prefect/events/filters.py +14 -18
  79. prefect/events/related.py +74 -75
  80. prefect/events/schemas/__init__.py +0 -5
  81. prefect/events/schemas/automations.py +55 -46
  82. prefect/events/schemas/deployment_triggers.py +7 -197
  83. prefect/events/schemas/events.py +46 -65
  84. prefect/events/schemas/labelling.py +10 -14
  85. prefect/events/utilities.py +4 -5
  86. prefect/events/worker.py +23 -8
  87. prefect/exceptions.py +15 -0
  88. prefect/filesystems.py +30 -529
  89. prefect/flow_engine.py +827 -0
  90. prefect/flow_runs.py +379 -7
  91. prefect/flows.py +470 -360
  92. prefect/futures.py +382 -331
  93. prefect/infrastructure/__init__.py +5 -26
  94. prefect/infrastructure/base.py +3 -320
  95. prefect/infrastructure/provisioners/__init__.py +5 -3
  96. prefect/infrastructure/provisioners/cloud_run.py +13 -8
  97. prefect/infrastructure/provisioners/container_instance.py +14 -9
  98. prefect/infrastructure/provisioners/ecs.py +10 -8
  99. prefect/infrastructure/provisioners/modal.py +8 -5
  100. prefect/input/__init__.py +4 -0
  101. prefect/input/actions.py +2 -4
  102. prefect/input/run_input.py +9 -9
  103. prefect/logging/formatters.py +2 -4
  104. prefect/logging/handlers.py +9 -14
  105. prefect/logging/loggers.py +5 -5
  106. prefect/main.py +72 -0
  107. prefect/plugins.py +2 -64
  108. prefect/profiles.toml +16 -2
  109. prefect/records/__init__.py +1 -0
  110. prefect/records/base.py +223 -0
  111. prefect/records/filesystem.py +207 -0
  112. prefect/records/memory.py +178 -0
  113. prefect/records/result_store.py +64 -0
  114. prefect/results.py +577 -504
  115. prefect/runner/runner.py +124 -51
  116. prefect/runner/server.py +32 -34
  117. prefect/runner/storage.py +3 -12
  118. prefect/runner/submit.py +2 -10
  119. prefect/runner/utils.py +2 -2
  120. prefect/runtime/__init__.py +1 -0
  121. prefect/runtime/deployment.py +1 -0
  122. prefect/runtime/flow_run.py +40 -5
  123. prefect/runtime/task_run.py +1 -0
  124. prefect/serializers.py +28 -39
  125. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
  126. prefect/settings.py +209 -332
  127. prefect/states.py +160 -63
  128. prefect/task_engine.py +1478 -57
  129. prefect/task_runners.py +383 -287
  130. prefect/task_runs.py +240 -0
  131. prefect/task_worker.py +463 -0
  132. prefect/tasks.py +684 -374
  133. prefect/transactions.py +410 -0
  134. prefect/types/__init__.py +72 -86
  135. prefect/types/entrypoint.py +13 -0
  136. prefect/utilities/annotations.py +4 -3
  137. prefect/utilities/asyncutils.py +227 -148
  138. prefect/utilities/callables.py +138 -48
  139. prefect/utilities/collections.py +134 -86
  140. prefect/utilities/dispatch.py +27 -14
  141. prefect/utilities/dockerutils.py +11 -4
  142. prefect/utilities/engine.py +186 -32
  143. prefect/utilities/filesystem.py +4 -5
  144. prefect/utilities/importtools.py +26 -27
  145. prefect/utilities/pydantic.py +128 -38
  146. prefect/utilities/schema_tools/hydration.py +18 -1
  147. prefect/utilities/schema_tools/validation.py +30 -0
  148. prefect/utilities/services.py +35 -9
  149. prefect/utilities/templating.py +12 -2
  150. prefect/utilities/timeout.py +20 -5
  151. prefect/utilities/urls.py +195 -0
  152. prefect/utilities/visualization.py +1 -0
  153. prefect/variables.py +78 -59
  154. prefect/workers/__init__.py +0 -1
  155. prefect/workers/base.py +237 -244
  156. prefect/workers/block.py +5 -226
  157. prefect/workers/cloud.py +6 -0
  158. prefect/workers/process.py +265 -12
  159. prefect/workers/server.py +29 -11
  160. {prefect_client-2.20.2.dist-info → prefect_client-3.0.0.dist-info}/METADATA +30 -26
  161. prefect_client-3.0.0.dist-info/RECORD +201 -0
  162. {prefect_client-2.20.2.dist-info → prefect_client-3.0.0.dist-info}/WHEEL +1 -1
  163. prefect/_internal/pydantic/_base_model.py +0 -51
  164. prefect/_internal/pydantic/_compat.py +0 -82
  165. prefect/_internal/pydantic/_flags.py +0 -20
  166. prefect/_internal/pydantic/_types.py +0 -8
  167. prefect/_internal/pydantic/utilities/config_dict.py +0 -72
  168. prefect/_internal/pydantic/utilities/field_validator.py +0 -150
  169. prefect/_internal/pydantic/utilities/model_construct.py +0 -56
  170. prefect/_internal/pydantic/utilities/model_copy.py +0 -55
  171. prefect/_internal/pydantic/utilities/model_dump.py +0 -136
  172. prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
  173. prefect/_internal/pydantic/utilities/model_fields.py +0 -50
  174. prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
  175. prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
  176. prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
  177. prefect/_internal/pydantic/utilities/model_validate.py +0 -75
  178. prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
  179. prefect/_internal/pydantic/utilities/model_validator.py +0 -87
  180. prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
  181. prefect/_vendor/fastapi/__init__.py +0 -25
  182. prefect/_vendor/fastapi/applications.py +0 -946
  183. prefect/_vendor/fastapi/background.py +0 -3
  184. prefect/_vendor/fastapi/concurrency.py +0 -44
  185. prefect/_vendor/fastapi/datastructures.py +0 -58
  186. prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
  187. prefect/_vendor/fastapi/dependencies/models.py +0 -64
  188. prefect/_vendor/fastapi/dependencies/utils.py +0 -877
  189. prefect/_vendor/fastapi/encoders.py +0 -177
  190. prefect/_vendor/fastapi/exception_handlers.py +0 -40
  191. prefect/_vendor/fastapi/exceptions.py +0 -46
  192. prefect/_vendor/fastapi/logger.py +0 -3
  193. prefect/_vendor/fastapi/middleware/__init__.py +0 -1
  194. prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
  195. prefect/_vendor/fastapi/middleware/cors.py +0 -3
  196. prefect/_vendor/fastapi/middleware/gzip.py +0 -3
  197. prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
  198. prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
  199. prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
  200. prefect/_vendor/fastapi/openapi/__init__.py +0 -0
  201. prefect/_vendor/fastapi/openapi/constants.py +0 -2
  202. prefect/_vendor/fastapi/openapi/docs.py +0 -203
  203. prefect/_vendor/fastapi/openapi/models.py +0 -480
  204. prefect/_vendor/fastapi/openapi/utils.py +0 -485
  205. prefect/_vendor/fastapi/param_functions.py +0 -340
  206. prefect/_vendor/fastapi/params.py +0 -453
  207. prefect/_vendor/fastapi/py.typed +0 -0
  208. prefect/_vendor/fastapi/requests.py +0 -4
  209. prefect/_vendor/fastapi/responses.py +0 -40
  210. prefect/_vendor/fastapi/routing.py +0 -1331
  211. prefect/_vendor/fastapi/security/__init__.py +0 -15
  212. prefect/_vendor/fastapi/security/api_key.py +0 -98
  213. prefect/_vendor/fastapi/security/base.py +0 -6
  214. prefect/_vendor/fastapi/security/http.py +0 -172
  215. prefect/_vendor/fastapi/security/oauth2.py +0 -227
  216. prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
  217. prefect/_vendor/fastapi/security/utils.py +0 -10
  218. prefect/_vendor/fastapi/staticfiles.py +0 -1
  219. prefect/_vendor/fastapi/templating.py +0 -3
  220. prefect/_vendor/fastapi/testclient.py +0 -1
  221. prefect/_vendor/fastapi/types.py +0 -3
  222. prefect/_vendor/fastapi/utils.py +0 -235
  223. prefect/_vendor/fastapi/websockets.py +0 -7
  224. prefect/_vendor/starlette/__init__.py +0 -1
  225. prefect/_vendor/starlette/_compat.py +0 -28
  226. prefect/_vendor/starlette/_exception_handler.py +0 -80
  227. prefect/_vendor/starlette/_utils.py +0 -88
  228. prefect/_vendor/starlette/applications.py +0 -261
  229. prefect/_vendor/starlette/authentication.py +0 -159
  230. prefect/_vendor/starlette/background.py +0 -43
  231. prefect/_vendor/starlette/concurrency.py +0 -59
  232. prefect/_vendor/starlette/config.py +0 -151
  233. prefect/_vendor/starlette/convertors.py +0 -87
  234. prefect/_vendor/starlette/datastructures.py +0 -707
  235. prefect/_vendor/starlette/endpoints.py +0 -130
  236. prefect/_vendor/starlette/exceptions.py +0 -60
  237. prefect/_vendor/starlette/formparsers.py +0 -276
  238. prefect/_vendor/starlette/middleware/__init__.py +0 -17
  239. prefect/_vendor/starlette/middleware/authentication.py +0 -52
  240. prefect/_vendor/starlette/middleware/base.py +0 -220
  241. prefect/_vendor/starlette/middleware/cors.py +0 -176
  242. prefect/_vendor/starlette/middleware/errors.py +0 -265
  243. prefect/_vendor/starlette/middleware/exceptions.py +0 -74
  244. prefect/_vendor/starlette/middleware/gzip.py +0 -113
  245. prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
  246. prefect/_vendor/starlette/middleware/sessions.py +0 -82
  247. prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
  248. prefect/_vendor/starlette/middleware/wsgi.py +0 -147
  249. prefect/_vendor/starlette/py.typed +0 -0
  250. prefect/_vendor/starlette/requests.py +0 -328
  251. prefect/_vendor/starlette/responses.py +0 -347
  252. prefect/_vendor/starlette/routing.py +0 -933
  253. prefect/_vendor/starlette/schemas.py +0 -154
  254. prefect/_vendor/starlette/staticfiles.py +0 -248
  255. prefect/_vendor/starlette/status.py +0 -199
  256. prefect/_vendor/starlette/templating.py +0 -231
  257. prefect/_vendor/starlette/testclient.py +0 -804
  258. prefect/_vendor/starlette/types.py +0 -30
  259. prefect/_vendor/starlette/websockets.py +0 -193
  260. prefect/blocks/kubernetes.py +0 -119
  261. prefect/deprecated/__init__.py +0 -0
  262. prefect/deprecated/data_documents.py +0 -350
  263. prefect/deprecated/packaging/__init__.py +0 -12
  264. prefect/deprecated/packaging/base.py +0 -96
  265. prefect/deprecated/packaging/docker.py +0 -146
  266. prefect/deprecated/packaging/file.py +0 -92
  267. prefect/deprecated/packaging/orion.py +0 -80
  268. prefect/deprecated/packaging/serializers.py +0 -171
  269. prefect/events/instrument.py +0 -135
  270. prefect/infrastructure/container.py +0 -824
  271. prefect/infrastructure/kubernetes.py +0 -920
  272. prefect/infrastructure/process.py +0 -289
  273. prefect/manifests.py +0 -20
  274. prefect/new_flow_engine.py +0 -449
  275. prefect/new_task_engine.py +0 -423
  276. prefect/pydantic/__init__.py +0 -76
  277. prefect/pydantic/main.py +0 -39
  278. prefect/software/__init__.py +0 -2
  279. prefect/software/base.py +0 -50
  280. prefect/software/conda.py +0 -199
  281. prefect/software/pip.py +0 -122
  282. prefect/software/python.py +0 -52
  283. prefect/task_server.py +0 -322
  284. prefect_client-2.20.2.dist-info/RECORD +0 -294
  285. /prefect/{_internal/pydantic/utilities → client/types}/__init__.py +0 -0
  286. /prefect/{_vendor → concurrency/v1}/__init__.py +0 -0
  287. {prefect_client-2.20.2.dist-info → prefect_client-3.0.0.dist-info}/LICENSE +0 -0
  288. {prefect_client-2.20.2.dist-info → prefect_client-3.0.0.dist-info}/top_level.txt +0 -0
prefect/blocks/core.py CHANGED
@@ -2,39 +2,46 @@ import hashlib
2
2
  import html
3
3
  import inspect
4
4
  import sys
5
+ import uuid
5
6
  import warnings
6
7
  from abc import ABC
8
+ from functools import partial
7
9
  from textwrap import dedent
8
10
  from typing import (
9
11
  TYPE_CHECKING,
10
12
  Any,
13
+ Callable,
14
+ ClassVar,
11
15
  Dict,
12
16
  FrozenSet,
13
17
  List,
14
18
  Optional,
19
+ Tuple,
15
20
  Type,
16
21
  TypeVar,
17
22
  Union,
23
+ get_origin,
18
24
  )
19
25
  from uuid import UUID, uuid4
20
26
 
21
- from griffe.dataclasses import Docstring
22
- from griffe.docstrings.dataclasses import DocstringSection, DocstringSectionKind
23
- from griffe.docstrings.parsers import Parser, parse
27
+ from griffe import Docstring, DocstringSection, DocstringSectionKind, Parser, parse
24
28
  from packaging.version import InvalidVersion, Version
25
-
26
- from prefect._internal.pydantic import HAS_PYDANTIC_V2
27
-
28
- if HAS_PYDANTIC_V2:
29
- from pydantic.v1 import BaseModel, HttpUrl, SecretBytes, SecretStr, ValidationError
30
- else:
31
- from pydantic import BaseModel, HttpUrl, SecretBytes, SecretStr, ValidationError
32
-
33
- from typing_extensions import ParamSpec, Self, get_args, get_origin
29
+ from pydantic import (
30
+ BaseModel,
31
+ ConfigDict,
32
+ HttpUrl,
33
+ PrivateAttr,
34
+ SecretBytes,
35
+ SecretStr,
36
+ SerializationInfo,
37
+ ValidationError,
38
+ model_serializer,
39
+ )
40
+ from pydantic.json_schema import GenerateJsonSchema
41
+ from typing_extensions import Literal, ParamSpec, Self, get_args
34
42
 
35
43
  import prefect
36
44
  import prefect.exceptions
37
- from prefect.blocks.fields import SecretDict
38
45
  from prefect.client.schemas import (
39
46
  DEFAULT_BLOCK_SCHEMA_VERSION,
40
47
  BlockDocument,
@@ -43,26 +50,27 @@ from prefect.client.schemas import (
43
50
  BlockTypeUpdate,
44
51
  )
45
52
  from prefect.client.utilities import inject_client
46
- from prefect.events.instrument import (
47
- ResourceTuple,
48
- emit_instance_method_called_event,
49
- instrument_instance_method_call,
50
- instrument_method_calls_on_class_instances,
51
- )
53
+ from prefect.events import emit_event
52
54
  from prefect.logging.loggers import disable_logger
55
+ from prefect.types import SecretDict
53
56
  from prefect.utilities.asyncutils import sync_compatible
54
- from prefect.utilities.collections import listrepr, remove_nested_keys
57
+ from prefect.utilities.collections import listrepr, remove_nested_keys, visit_collection
55
58
  from prefect.utilities.dispatch import lookup_type, register_base_type
56
59
  from prefect.utilities.hashing import hash_objects
57
60
  from prefect.utilities.importtools import to_qualified_name
61
+ from prefect.utilities.pydantic import handle_secret_render
58
62
  from prefect.utilities.slugify import slugify
59
63
 
60
64
  if TYPE_CHECKING:
65
+ from pydantic.main import IncEx
66
+
61
67
  from prefect.client.orchestration import PrefectClient
62
68
 
63
69
  R = TypeVar("R")
64
70
  P = ParamSpec("P")
65
71
 
72
+ ResourceTuple = Tuple[Dict[str, Any], List[Dict[str, Any]]]
73
+
66
74
 
67
75
  def block_schema_to_key(schema: BlockSchema) -> str:
68
76
  """
@@ -120,32 +128,42 @@ def _is_subclass(cls, parent_cls) -> bool:
120
128
  Checks if a given class is a subclass of another class. Unlike issubclass,
121
129
  this will not throw an exception if cls is an instance instead of a type.
122
130
  """
123
- return inspect.isclass(cls) and issubclass(cls, parent_cls)
131
+ # For python<=3.11 inspect.isclass() will return True for parametrized types (e.g. list[str])
132
+ # so we need to check for get_origin() to avoid TypeError for issubclass.
133
+ return inspect.isclass(cls) and not get_origin(cls) and issubclass(cls, parent_cls)
124
134
 
125
135
 
126
- def _collect_secret_fields(name: str, type_: Type, secrets: List[str]) -> None:
136
+ def _collect_secret_fields(
137
+ name: str, type_: Type[BaseModel], secrets: List[str]
138
+ ) -> None:
127
139
  """
128
140
  Recursively collects all secret fields from a given type and adds them to the
129
- secrets list, supporting nested Union / BaseModel fields. Also, note, this function
130
- mutates the input secrets list, thus does not return anything.
141
+ secrets list, supporting nested Union / Dict / Tuple / List / BaseModel fields.
142
+ Also, note, this function mutates the input secrets list, thus does not return anything.
131
143
  """
132
- if get_origin(type_) is Union:
133
- for union_type in get_args(type_):
134
- _collect_secret_fields(name, union_type, secrets)
144
+ if get_origin(type_) in (Union, dict, list, tuple):
145
+ for nested_type in get_args(type_):
146
+ _collect_secret_fields(name, nested_type, secrets)
135
147
  return
136
148
  elif _is_subclass(type_, BaseModel):
137
- for field in type_.__fields__.values():
138
- _collect_secret_fields(f"{name}.{field.name}", field.type_, secrets)
149
+ for field_name, field in type_.model_fields.items():
150
+ _collect_secret_fields(f"{name}.{field_name}", field.annotation, secrets)
139
151
  return
140
152
 
141
- if type_ in (SecretStr, SecretBytes):
153
+ if type_ in (SecretStr, SecretBytes) or (
154
+ isinstance(type_, type)
155
+ and getattr(type_, "__module__", None) == "pydantic.types"
156
+ and getattr(type_, "__name__", None) == "Secret"
157
+ ):
142
158
  secrets.append(name)
143
159
  elif type_ == SecretDict:
144
160
  # Append .* to field name to signify that all values under this
145
161
  # field are secret and should be obfuscated.
146
162
  secrets.append(f"{name}.*")
147
163
  elif Block.is_block_class(type_):
148
- secrets.extend(f"{name}.{s}" for s in type_.schema()["secret_fields"])
164
+ secrets.extend(
165
+ f"{name}.{s}" for s in type_.model_json_schema()["secret_fields"]
166
+ )
149
167
 
150
168
 
151
169
  def _should_update_block_type(
@@ -158,8 +176,10 @@ def _should_update_block_type(
158
176
  """
159
177
  fields = BlockTypeUpdate.updatable_fields()
160
178
 
161
- local_block_fields = local_block_type.dict(include=fields, exclude_unset=True)
162
- server_block_fields = server_block_type.dict(include=fields, exclude_unset=True)
179
+ local_block_fields = local_block_type.model_dump(include=fields, exclude_unset=True)
180
+ server_block_fields = server_block_type.model_dump(
181
+ include=fields, exclude_unset=True
182
+ )
163
183
 
164
184
  if local_block_fields.get("description") is not None:
165
185
  local_block_fields["description"] = html.unescape(
@@ -191,8 +211,53 @@ class BlockNotSavedError(RuntimeError):
191
211
  pass
192
212
 
193
213
 
214
+ def schema_extra(schema: Dict[str, Any], model: Type["Block"]):
215
+ """
216
+ Customizes Pydantic's schema generation feature to add blocks related information.
217
+ """
218
+ schema["block_type_slug"] = model.get_block_type_slug()
219
+ # Ensures args and code examples aren't included in the schema
220
+ description = model.get_description()
221
+ if description:
222
+ schema["description"] = description
223
+ else:
224
+ # Prevent the description of the base class from being included in the schema
225
+ schema.pop("description", None)
226
+
227
+ # create a list of secret field names
228
+ # secret fields include both top-level keys and dot-delimited nested secret keys
229
+ # A wildcard (*) means that all fields under a given key are secret.
230
+ # for example: ["x", "y", "z.*", "child.a"]
231
+ # means the top-level keys "x" and "y", all keys under "z", and the key "a" of a block
232
+ # nested under the "child" key are all secret. There is no limit to nesting.
233
+ secrets = schema["secret_fields"] = []
234
+ for name, field in model.model_fields.items():
235
+ _collect_secret_fields(name, field.annotation, secrets)
236
+
237
+ # create block schema references
238
+ refs = schema["block_schema_references"] = {}
239
+
240
+ def collect_block_schema_references(field_name: str, annotation: type) -> None:
241
+ """Walk through the annotation and collect block schemas for any nested blocks."""
242
+ if Block.is_block_class(annotation):
243
+ if isinstance(refs.get(field_name), list):
244
+ refs[field_name].append(annotation._to_block_schema_reference_dict())
245
+ elif isinstance(refs.get(field_name), dict):
246
+ refs[field_name] = [
247
+ refs[field_name],
248
+ annotation._to_block_schema_reference_dict(),
249
+ ]
250
+ else:
251
+ refs[field_name] = annotation._to_block_schema_reference_dict()
252
+ if get_origin(annotation) in (Union, list, tuple, dict):
253
+ for type_ in get_args(annotation):
254
+ collect_block_schema_references(field_name, type_)
255
+
256
+ for name, field in model.model_fields.items():
257
+ collect_block_schema_references(name, field.annotation)
258
+
259
+
194
260
  @register_base_type
195
- @instrument_method_calls_on_class_instances
196
261
  class Block(BaseModel, ABC):
197
262
  """
198
263
  A base class for implementing a block that wraps an external service.
@@ -210,56 +275,10 @@ class Block(BaseModel, ABC):
210
275
  initialization.
211
276
  """
212
277
 
213
- class Config:
214
- extra = "allow"
215
-
216
- json_encoders = {SecretDict: lambda v: v.dict()}
217
-
218
- @staticmethod
219
- def schema_extra(schema: Dict[str, Any], model: Type["Block"]):
220
- """
221
- Customizes Pydantic's schema generation feature to add blocks related information.
222
- """
223
- schema["block_type_slug"] = model.get_block_type_slug()
224
- # Ensures args and code examples aren't included in the schema
225
- description = model.get_description()
226
- if description:
227
- schema["description"] = description
228
- else:
229
- # Prevent the description of the base class from being included in the schema
230
- schema.pop("description", None)
231
-
232
- # create a list of secret field names
233
- # secret fields include both top-level keys and dot-delimited nested secret keys
234
- # A wildcard (*) means that all fields under a given key are secret.
235
- # for example: ["x", "y", "z.*", "child.a"]
236
- # means the top-level keys "x" and "y", all keys under "z", and the key "a" of a block
237
- # nested under the "child" key are all secret. There is no limit to nesting.
238
- secrets = schema["secret_fields"] = []
239
- for field in model.__fields__.values():
240
- _collect_secret_fields(field.name, field.type_, secrets)
241
-
242
- # create block schema references
243
- refs = schema["block_schema_references"] = {}
244
- for field in model.__fields__.values():
245
- if Block.is_block_class(field.type_):
246
- refs[field.name] = field.type_._to_block_schema_reference_dict()
247
- if get_origin(field.type_) is Union:
248
- for type_ in get_args(field.type_):
249
- if Block.is_block_class(type_):
250
- if isinstance(refs.get(field.name), list):
251
- refs[field.name].append(
252
- type_._to_block_schema_reference_dict()
253
- )
254
- elif isinstance(refs.get(field.name), dict):
255
- refs[field.name] = [
256
- refs[field.name],
257
- type_._to_block_schema_reference_dict(),
258
- ]
259
- else:
260
- refs[
261
- field.name
262
- ] = type_._to_block_schema_reference_dict()
278
+ model_config = ConfigDict(
279
+ extra="allow",
280
+ json_schema_extra=schema_extra,
281
+ )
263
282
 
264
283
  def __init__(self, *args, **kwargs):
265
284
  super().__init__(*args, **kwargs)
@@ -270,7 +289,7 @@ class Block(BaseModel, ABC):
270
289
 
271
290
  def __repr_args__(self):
272
291
  repr_args = super().__repr_args__()
273
- data_keys = self.schema()["properties"].keys()
292
+ data_keys = self.model_json_schema()["properties"].keys()
274
293
  return [
275
294
  (key, value) for key, value in repr_args if key is None or key in data_keys
276
295
  ]
@@ -284,25 +303,26 @@ class Block(BaseModel, ABC):
284
303
  # Attribute to customize the name of the block type created
285
304
  # when the block is registered with the API. If not set, block
286
305
  # type name will default to the class name.
287
- _block_type_name: Optional[str] = None
288
- _block_type_slug: Optional[str] = None
306
+ _block_type_name: ClassVar[Optional[str]] = None
307
+ _block_type_slug: ClassVar[Optional[str]] = None
289
308
 
290
309
  # Attributes used to set properties on a block type when registered
291
310
  # with the API.
292
- _logo_url: Optional[HttpUrl] = None
293
- _documentation_url: Optional[HttpUrl] = None
294
- _description: Optional[str] = None
295
- _code_example: Optional[str] = None
311
+ _logo_url: ClassVar[Optional[HttpUrl]] = None
312
+ _documentation_url: ClassVar[Optional[HttpUrl]] = None
313
+ _description: ClassVar[Optional[str]] = None
314
+ _code_example: ClassVar[Optional[str]] = None
315
+ _block_type_id: ClassVar[Optional[UUID]] = None
316
+ _block_schema_id: ClassVar[Optional[UUID]] = None
317
+ _block_schema_capabilities: ClassVar[Optional[List[str]]] = None
318
+ _block_schema_version: ClassVar[Optional[str]] = None
296
319
 
297
320
  # -- private instance variables
298
321
  # these are set when blocks are loaded from the API
299
- _block_type_id: Optional[UUID] = None
300
- _block_schema_id: Optional[UUID] = None
301
- _block_schema_capabilities: Optional[List[str]] = None
302
- _block_schema_version: Optional[str] = None
303
- _block_document_id: Optional[UUID] = None
304
- _block_document_name: Optional[str] = None
305
- _is_anonymous: Optional[bool] = None
322
+
323
+ _block_document_id: Optional[UUID] = PrivateAttr(None)
324
+ _block_document_name: Optional[str] = PrivateAttr(None)
325
+ _is_anonymous: Optional[bool] = PrivateAttr(None)
306
326
 
307
327
  # Exclude `save` as it uses the `sync_compatible` decorator and needs to be
308
328
  # decorated directly.
@@ -314,6 +334,31 @@ class Block(BaseModel, ABC):
314
334
  return None # The base class is abstract
315
335
  return block_schema_to_key(cls._to_block_schema())
316
336
 
337
+ @model_serializer(mode="wrap")
338
+ def ser_model(self, handler: Callable, info: SerializationInfo) -> Any:
339
+ jsonable_self = handler(self)
340
+ if (ctx := info.context) and ctx.get("include_secrets") is True:
341
+ jsonable_self.update(
342
+ {
343
+ field_name: visit_collection(
344
+ expr=getattr(self, field_name),
345
+ visit_fn=partial(handle_secret_render, context=ctx),
346
+ return_data=True,
347
+ )
348
+ for field_name in self.model_fields
349
+ }
350
+ )
351
+ if extra_fields := {
352
+ "block_type_slug": self.get_block_type_slug(),
353
+ "_block_document_id": self._block_document_id,
354
+ "_block_document_name": self._block_document_name,
355
+ "_is_anonymous": self._is_anonymous,
356
+ }:
357
+ jsonable_self |= {
358
+ key: value for key, value in extra_fields.items() if value is not None
359
+ }
360
+ return jsonable_self
361
+
317
362
  @classmethod
318
363
  def get_block_type_name(cls):
319
364
  return cls._block_type_name or cls.__name__
@@ -379,7 +424,9 @@ class Block(BaseModel, ABC):
379
424
  str: The calculated checksum prefixed with the hashing algorithm used.
380
425
  """
381
426
  block_schema_fields = (
382
- cls.schema() if block_schema_fields is None else block_schema_fields
427
+ cls.model_json_schema()
428
+ if block_schema_fields is None
429
+ else block_schema_fields
383
430
  )
384
431
  fields_for_checksum = remove_nested_keys(["secret_fields"], block_schema_fields)
385
432
  if fields_for_checksum.get("definitions"):
@@ -404,6 +451,7 @@ class Block(BaseModel, ABC):
404
451
  block_schema_id: Optional[UUID] = None,
405
452
  block_type_id: Optional[UUID] = None,
406
453
  is_anonymous: Optional[bool] = None,
454
+ include_secrets: bool = False,
407
455
  ) -> BlockDocument:
408
456
  """
409
457
  Creates the corresponding block document based on the data stored in a block.
@@ -441,10 +489,14 @@ class Block(BaseModel, ABC):
441
489
  # The keys passed to `include` must NOT be aliases, else some items will be missed
442
490
  # i.e. must do `self.schema_` vs `self.schema` to get a `schema_ = Field(alias="schema")`
443
491
  # reported from https://github.com/PrefectHQ/prefect-dbt/issues/54
444
- data_keys = self.schema(by_alias=False)["properties"].keys()
492
+ data_keys = self.model_json_schema(by_alias=False)["properties"].keys()
445
493
 
446
494
  # `block_document_data`` must return the aliased version for it to show in the UI
447
- block_document_data = self.dict(by_alias=True, include=data_keys)
495
+ block_document_data = self.model_dump(
496
+ by_alias=True,
497
+ include=data_keys,
498
+ context={"include_secrets": include_secrets},
499
+ )
448
500
 
449
501
  # Iterate through and find blocks that already have saved block documents to
450
502
  # create references to those saved block documents.
@@ -484,7 +536,7 @@ class Block(BaseModel, ABC):
484
536
  Returns:
485
537
  BlockSchema: The corresponding block schema.
486
538
  """
487
- fields = cls.schema()
539
+ fields = cls.model_json_schema()
488
540
  return BlockSchema(
489
541
  id=cls._block_schema_id if cls._block_schema_id is not None else uuid4(),
490
542
  checksum=cls._calculate_schema_checksum(),
@@ -539,7 +591,7 @@ class Block(BaseModel, ABC):
539
591
  code example from the class docstring if an override is not provided.
540
592
  """
541
593
  code_example = (
542
- dedent(cls._code_example) if cls._code_example is not None else None
594
+ dedent(text=cls._code_example) if cls._code_example is not None else None
543
595
  )
544
596
  # If no code example override has been provided, attempt to find a examples
545
597
  # section or an admonition with the annotation "example" and use that as the
@@ -606,7 +658,7 @@ class Block(BaseModel, ABC):
606
658
  )
607
659
 
608
660
  @classmethod
609
- def _from_block_document(cls, block_document: BlockDocument):
661
+ def _from_block_document(cls, block_document: BlockDocument) -> Self:
610
662
  """
611
663
  Instantiates a block from a given block document. The corresponding block class
612
664
  will be looked up in the block registry based on the corresponding block schema
@@ -634,9 +686,7 @@ class Block(BaseModel, ABC):
634
686
  else cls.get_block_class_from_schema(block_document.block_schema)
635
687
  )
636
688
 
637
- block_cls = instrument_method_calls_on_class_instances(block_cls)
638
-
639
- block = block_cls.parse_obj(block_document.data)
689
+ block = block_cls.model_validate(block_document.data)
640
690
  block._block_document_id = block_document.id
641
691
  block.__class__._block_schema_id = block_document.block_schema_id
642
692
  block.__class__._block_type_id = block_document.block_type_id
@@ -646,13 +696,11 @@ class Block(BaseModel, ABC):
646
696
  block_document.block_document_references
647
697
  )
648
698
 
649
- # Due to the way blocks are loaded we can't directly instrument the
650
- # `load` method and have the data be about the block document. Instead
651
- # this will emit a proxy event for the load method so that block
652
- # document data can be included instead of the event being about an
653
- # 'anonymous' block.
654
-
655
- emit_instance_method_called_event(block, "load", successful=True)
699
+ resources: Optional[ResourceTuple] = block._event_method_called_resources()
700
+ if resources:
701
+ kind = block._event_kind()
702
+ resource, related = resources
703
+ emit_event(event=f"{kind}.loaded", resource=resource, related=related)
656
704
 
657
705
  return block
658
706
 
@@ -731,7 +779,7 @@ class Block(BaseModel, ABC):
731
779
  async def _get_block_document(
732
780
  cls,
733
781
  name: str,
734
- client: "PrefectClient" = None,
782
+ client: Optional["PrefectClient"] = None,
735
783
  ):
736
784
  if cls.__name__ == "Block":
737
785
  block_type_slug, block_document_name = name.split("/", 1)
@@ -751,6 +799,33 @@ class Block(BaseModel, ABC):
751
799
 
752
800
  return block_document, block_document_name
753
801
 
802
+ @classmethod
803
+ @sync_compatible
804
+ @inject_client
805
+ async def _get_block_document_by_id(
806
+ cls,
807
+ block_document_id: Union[str, uuid.UUID],
808
+ client: Optional["PrefectClient"] = None,
809
+ ):
810
+ if isinstance(block_document_id, str):
811
+ try:
812
+ block_document_id = UUID(block_document_id)
813
+ except ValueError:
814
+ raise ValueError(
815
+ f"Block document ID {block_document_id!r} is not a valid UUID"
816
+ )
817
+
818
+ try:
819
+ block_document = await client.read_block_document(
820
+ block_document_id=block_document_id
821
+ )
822
+ except prefect.exceptions.ObjectNotFound:
823
+ raise ValueError(
824
+ f"Unable to find block document with ID {block_document_id!r}"
825
+ )
826
+
827
+ return block_document, block_document.name
828
+
754
829
  @classmethod
755
830
  @sync_compatible
756
831
  @inject_client
@@ -758,8 +833,8 @@ class Block(BaseModel, ABC):
758
833
  cls,
759
834
  name: str,
760
835
  validate: bool = True,
761
- client: "PrefectClient" = None,
762
- ):
836
+ client: Optional["PrefectClient"] = None,
837
+ ) -> "Self":
763
838
  """
764
839
  Retrieves data from the block document with the given name for the block type
765
840
  that corresponds with the current class and returns an instantiated version of
@@ -835,8 +910,108 @@ class Block(BaseModel, ABC):
835
910
  loaded_block.save("my-custom-message", overwrite=True)
836
911
  ```
837
912
  """
838
- block_document, block_document_name = await cls._get_block_document(name)
913
+ block_document, block_document_name = await cls._get_block_document(
914
+ name, client=client
915
+ )
916
+
917
+ return cls._load_from_block_document(block_document, validate=validate)
918
+
919
+ @classmethod
920
+ @sync_compatible
921
+ @inject_client
922
+ async def load_from_ref(
923
+ cls,
924
+ ref: Union[str, UUID, Dict[str, Any]],
925
+ validate: bool = True,
926
+ client: Optional["PrefectClient"] = None,
927
+ ) -> "Self":
928
+ """
929
+ Retrieves data from the block document by given reference for the block type
930
+ that corresponds with the current class and returns an instantiated version of
931
+ the current class with the data stored in the block document.
932
+
933
+ Provided reference can be a block document ID, or a reference data in dictionary format.
934
+ Supported dictionary reference formats are:
935
+ - {"block_document_id": <block_document_id>}
936
+ - {"block_document_slug": <block_document_slug>}
937
+
938
+ If a block document for a given block type is saved with a different schema
939
+ than the current class calling `load`, a warning will be raised.
940
+
941
+ If the current class schema is a subset of the block document schema, the block
942
+ can be loaded as normal using the default `validate = True`.
943
+
944
+ If the current class schema is a superset of the block document schema, `load`
945
+ must be called with `validate` set to False to prevent a validation error. In
946
+ this case, the block attributes will default to `None` and must be set manually
947
+ and saved to a new block document before the block can be used as expected.
948
+
949
+ Args:
950
+ ref: The reference to the block document. This can be a block document ID,
951
+ or one of supported dictionary reference formats.
952
+ validate: If False, the block document will be loaded without Pydantic
953
+ validating the block schema. This is useful if the block schema has
954
+ changed client-side since the block document referred to by `name` was saved.
955
+ client: The client to use to load the block document. If not provided, the
956
+ default client will be injected.
957
+
958
+ Raises:
959
+ ValueError: If invalid reference format is provided.
960
+ ValueError: If the requested block document is not found.
961
+
962
+ Returns:
963
+ An instance of the current class hydrated with the data stored in the
964
+ block document with the specified name.
965
+
966
+ """
967
+ block_document = None
968
+ if isinstance(ref, (str, UUID)):
969
+ block_document, _ = await cls._get_block_document_by_id(ref)
970
+ elif isinstance(ref, dict):
971
+ if block_document_id := ref.get("block_document_id"):
972
+ block_document, _ = await cls._get_block_document_by_id(
973
+ block_document_id
974
+ )
975
+ elif block_document_slug := ref.get("block_document_slug"):
976
+ block_document, _ = await cls._get_block_document(block_document_slug)
977
+
978
+ if not block_document:
979
+ raise ValueError(f"Invalid reference format {ref!r}.")
980
+
981
+ return cls._load_from_block_document(block_document, validate=validate)
982
+
983
+ @classmethod
984
+ def _load_from_block_document(
985
+ cls, block_document: BlockDocument, validate: bool = True
986
+ ) -> "Self":
987
+ """
988
+ Loads a block from a given block document.
839
989
 
990
+ If a block document for a given block type is saved with a different schema
991
+ than the current class calling `load`, a warning will be raised.
992
+
993
+ If the current class schema is a subset of the block document schema, the block
994
+ can be loaded as normal using the default `validate = True`.
995
+
996
+ If the current class schema is a superset of the block document schema, `load`
997
+ must be called with `validate` set to False to prevent a validation error. In
998
+ this case, the block attributes will default to `None` and must be set manually
999
+ and saved to a new block document before the block can be used as expected.
1000
+
1001
+ Args:
1002
+ block_document: The block document used to instantiate a block.
1003
+ validate: If False, the block document will be loaded without Pydantic
1004
+ validating the block schema. This is useful if the block schema has
1005
+ changed client-side since the block document referred to by `name` was saved.
1006
+
1007
+ Raises:
1008
+ ValueError: If the requested block document is not found.
1009
+
1010
+ Returns:
1011
+ An instance of the current class hydrated with the data stored in the
1012
+ block document with the specified name.
1013
+
1014
+ """
840
1015
  try:
841
1016
  return cls._from_block_document(block_document)
842
1017
  except ValidationError as e:
@@ -844,19 +1019,19 @@ class Block(BaseModel, ABC):
844
1019
  missing_fields = tuple(err["loc"][0] for err in e.errors())
845
1020
  missing_block_data = {field: None for field in missing_fields}
846
1021
  warnings.warn(
847
- f"Could not fully load {block_document_name!r} of block type"
848
- f" {cls._block_type_slug!r} - this is likely because one or more"
1022
+ f"Could not fully load {block_document.name!r} of block type"
1023
+ f" {cls.get_block_type_slug()!r} - this is likely because one or more"
849
1024
  " required fields were added to the schema for"
850
1025
  f" {cls.__name__!r} that did not exist on the class when this block"
851
1026
  " was last saved. Please specify values for new field(s):"
852
1027
  f" {listrepr(missing_fields)}, then run"
853
- f' `{cls.__name__}.save("{block_document_name}", overwrite=True)`,'
1028
+ f' `{cls.__name__}.save("{block_document.name}", overwrite=True)`,'
854
1029
  " and load this block again before attempting to use it."
855
1030
  )
856
- return cls.construct(**block_document.data, **missing_block_data)
1031
+ return cls.model_construct(**block_document.data, **missing_block_data)
857
1032
  raise RuntimeError(
858
- f"Unable to load {block_document_name!r} of block type"
859
- f" {cls._block_type_slug!r} due to failed validation. To load without"
1033
+ f"Unable to load {block_document.name!r} of block type"
1034
+ f" {cls.get_block_type_slug()!r} due to failed validation. To load without"
860
1035
  " validation, try loading again with `validate=False`."
861
1036
  ) from e
862
1037
 
@@ -864,10 +1039,22 @@ class Block(BaseModel, ABC):
864
1039
  def is_block_class(block) -> bool:
865
1040
  return _is_subclass(block, Block)
866
1041
 
1042
+ @staticmethod
1043
+ def annotation_refers_to_block_class(annotation: Any) -> bool:
1044
+ if Block.is_block_class(annotation):
1045
+ return True
1046
+
1047
+ if get_origin(annotation) is Union:
1048
+ for annotation in get_args(annotation):
1049
+ if Block.is_block_class(annotation):
1050
+ return True
1051
+
1052
+ return False
1053
+
867
1054
  @classmethod
868
1055
  @sync_compatible
869
1056
  @inject_client
870
- async def register_type_and_schema(cls, client: "PrefectClient" = None):
1057
+ async def register_type_and_schema(cls, client: Optional["PrefectClient"] = None):
871
1058
  """
872
1059
  Makes block available for configuration with current Prefect API.
873
1060
  Recursively registers all nested blocks. Registration is idempotent.
@@ -888,19 +1075,24 @@ class Block(BaseModel, ABC):
888
1075
  "subclass and not on a Block interface class directly."
889
1076
  )
890
1077
 
891
- for field in cls.__fields__.values():
892
- if Block.is_block_class(field.type_):
893
- await field.type_.register_type_and_schema(client=client)
894
- if get_origin(field.type_) is Union:
895
- for type_ in get_args(field.type_):
896
- if Block.is_block_class(type_):
897
- await type_.register_type_and_schema(client=client)
1078
+ async def register_blocks_in_annotation(annotation: type) -> None:
1079
+ """Walk through the annotation and register any nested blocks."""
1080
+ if Block.is_block_class(annotation):
1081
+ await annotation.register_type_and_schema(client=client)
1082
+ elif get_origin(annotation) in (Union, tuple, list, dict):
1083
+ for inner_annotation in get_args(annotation):
1084
+ await register_blocks_in_annotation(inner_annotation)
1085
+
1086
+ for field in cls.model_fields.values():
1087
+ await register_blocks_in_annotation(field.annotation)
898
1088
 
899
1089
  try:
900
1090
  block_type = await client.read_block_type_by_slug(
901
1091
  slug=cls.get_block_type_slug()
902
1092
  )
1093
+
903
1094
  cls._block_type_id = block_type.id
1095
+
904
1096
  local_block_type = cls._to_block_type()
905
1097
  if _should_update_block_type(
906
1098
  local_block_type=local_block_type, server_block_type=block_type
@@ -930,7 +1122,7 @@ class Block(BaseModel, ABC):
930
1122
  name: Optional[str] = None,
931
1123
  is_anonymous: bool = False,
932
1124
  overwrite: bool = False,
933
- client: "PrefectClient" = None,
1125
+ client: Optional["PrefectClient"] = None,
934
1126
  ):
935
1127
  """
936
1128
  Saves the values of a block as a block document with an option to save as an
@@ -978,7 +1170,9 @@ class Block(BaseModel, ABC):
978
1170
  block_document_id = existing_block_document.id
979
1171
  await client.update_block_document(
980
1172
  block_document_id=block_document_id,
981
- block_document=self._to_block_document(name=name),
1173
+ block_document=self._to_block_document(
1174
+ name=name, include_secrets=True
1175
+ ),
982
1176
  )
983
1177
  block_document = await client.read_block_document(
984
1178
  block_document_id=block_document_id
@@ -996,12 +1190,11 @@ class Block(BaseModel, ABC):
996
1190
  return self._block_document_id
997
1191
 
998
1192
  @sync_compatible
999
- @instrument_instance_method_call
1000
1193
  async def save(
1001
1194
  self,
1002
1195
  name: Optional[str] = None,
1003
1196
  overwrite: bool = False,
1004
- client: "PrefectClient" = None,
1197
+ client: Optional["PrefectClient"] = None,
1005
1198
  ):
1006
1199
  """
1007
1200
  Saves the values of a block as a block document.
@@ -1023,25 +1216,12 @@ class Block(BaseModel, ABC):
1023
1216
  async def delete(
1024
1217
  cls,
1025
1218
  name: str,
1026
- client: "PrefectClient" = None,
1219
+ client: Optional["PrefectClient"] = None,
1027
1220
  ):
1028
1221
  block_document, block_document_name = await cls._get_block_document(name)
1029
1222
 
1030
1223
  await client.delete_block_document(block_document.id)
1031
1224
 
1032
- def _iter(self, *, include=None, exclude=None, **kwargs):
1033
- # Injects the `block_type_slug` into serialized payloads for dispatch
1034
- for key_value in super()._iter(include=include, exclude=exclude, **kwargs):
1035
- yield key_value
1036
-
1037
- # Respect inclusion and exclusion still
1038
- if include and "block_type_slug" not in include:
1039
- return
1040
- if exclude and "block_type_slug" in exclude:
1041
- return
1042
-
1043
- yield "block_type_slug", self.get_block_type_slug()
1044
-
1045
1225
  def __new__(cls: Type[Self], **kwargs) -> Self:
1046
1226
  """
1047
1227
  Create an instance of the Block subclass type if a `block_type_slug` is
@@ -1050,21 +1230,9 @@ class Block(BaseModel, ABC):
1050
1230
  block_type_slug = kwargs.pop("block_type_slug", None)
1051
1231
  if block_type_slug:
1052
1232
  subcls = lookup_type(cls, dispatch_key=block_type_slug)
1053
- m = super().__new__(subcls)
1054
- # NOTE: This is a workaround for an obscure issue where copied models were
1055
- # missing attributes. This pattern is from Pydantic's
1056
- # `BaseModel._copy_and_set_values`.
1057
- # The issue this fixes could not be reproduced in unit tests that
1058
- # directly targeted dispatch handling and was only observed when
1059
- # copying then saving infrastructure blocks on deployment models.
1060
- object.__setattr__(m, "__dict__", kwargs)
1061
- object.__setattr__(m, "__fields_set__", set(kwargs.keys()))
1062
- return m
1233
+ return super().__new__(subcls)
1063
1234
  else:
1064
- m = super().__new__(cls)
1065
- object.__setattr__(m, "__dict__", kwargs)
1066
- object.__setattr__(m, "__fields_set__", set(kwargs.keys()))
1067
- return m
1235
+ return super().__new__(cls)
1068
1236
 
1069
1237
  def get_block_placeholder(self) -> str:
1070
1238
  """
@@ -1087,3 +1255,94 @@ class Block(BaseModel, ABC):
1087
1255
  )
1088
1256
 
1089
1257
  return f"prefect.blocks.{self.get_block_type_slug()}.{block_document_name}"
1258
+
1259
+ @classmethod
1260
+ def model_json_schema(
1261
+ cls,
1262
+ by_alias: bool = True,
1263
+ ref_template: str = "#/definitions/{model}",
1264
+ schema_generator: Type[GenerateJsonSchema] = GenerateJsonSchema,
1265
+ mode: Literal["validation", "serialization"] = "validation",
1266
+ ) -> Dict[str, Any]:
1267
+ """TODO: stop overriding this method - use GenerateSchema in ConfigDict instead?"""
1268
+ schema = super().model_json_schema(
1269
+ by_alias, ref_template, schema_generator, mode
1270
+ )
1271
+
1272
+ # ensure backwards compatibility by copying $defs into definitions
1273
+ if "$defs" in schema:
1274
+ schema["definitions"] = schema.pop("$defs")
1275
+
1276
+ # we aren't expecting these additional fields in the schema
1277
+ if "additionalProperties" in schema:
1278
+ schema.pop("additionalProperties")
1279
+
1280
+ for _, definition in schema.get("definitions", {}).items():
1281
+ if "additionalProperties" in definition:
1282
+ definition.pop("additionalProperties")
1283
+
1284
+ return schema
1285
+
1286
+ @classmethod
1287
+ def model_validate(
1288
+ cls: type[Self],
1289
+ obj: Any,
1290
+ *,
1291
+ strict: Optional[bool] = None,
1292
+ from_attributes: Optional[bool] = None,
1293
+ context: Optional[Dict[str, Any]] = None,
1294
+ ) -> Self:
1295
+ if isinstance(obj, dict):
1296
+ extra_serializer_fields = {
1297
+ "_block_document_id",
1298
+ "_block_document_name",
1299
+ "_is_anonymous",
1300
+ }.intersection(obj.keys())
1301
+ for field in extra_serializer_fields:
1302
+ obj.pop(field, None)
1303
+
1304
+ return super().model_validate(
1305
+ obj, strict=strict, from_attributes=from_attributes, context=context
1306
+ )
1307
+
1308
+ def model_dump(
1309
+ self,
1310
+ *,
1311
+ mode: Union[Literal["json", "python"], str] = "python",
1312
+ include: "IncEx" = None,
1313
+ exclude: "IncEx" = None,
1314
+ context: Optional[Dict[str, Any]] = None,
1315
+ by_alias: bool = False,
1316
+ exclude_unset: bool = False,
1317
+ exclude_defaults: bool = False,
1318
+ exclude_none: bool = False,
1319
+ round_trip: bool = False,
1320
+ warnings: Union[bool, Literal["none", "warn", "error"]] = True,
1321
+ serialize_as_any: bool = False,
1322
+ ) -> Dict[str, Any]:
1323
+ d = super().model_dump(
1324
+ mode=mode,
1325
+ include=include,
1326
+ exclude=exclude,
1327
+ context=context,
1328
+ by_alias=by_alias,
1329
+ exclude_unset=exclude_unset,
1330
+ exclude_defaults=exclude_defaults,
1331
+ exclude_none=exclude_none,
1332
+ round_trip=round_trip,
1333
+ warnings=warnings,
1334
+ serialize_as_any=serialize_as_any,
1335
+ )
1336
+
1337
+ extra_serializer_fields = {
1338
+ "block_type_slug",
1339
+ "_block_document_id",
1340
+ "_block_document_name",
1341
+ "_is_anonymous",
1342
+ }.intersection(d.keys())
1343
+
1344
+ for field in extra_serializer_fields:
1345
+ if (include and field not in include) or (exclude and field in exclude):
1346
+ d.pop(field)
1347
+
1348
+ return d