prefect-client 2.20.4__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 +405 -153
  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 +650 -442
  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 -2475
  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 +117 -47
  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 +137 -45
  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.4.dist-info → prefect_client-3.0.0.dist-info}/METADATA +28 -24
  161. prefect_client-3.0.0.dist-info/RECORD +201 -0
  162. {prefect_client-2.20.4.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.4.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.4.dist-info → prefect_client-3.0.0.dist-info}/LICENSE +0 -0
  288. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/top_level.txt +0 -0
prefect/blocks/core.py CHANGED
@@ -2,37 +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
27
  from griffe import Docstring, DocstringSection, DocstringSectionKind, Parser, parse
22
28
  from packaging.version import InvalidVersion, Version
23
-
24
- from prefect._internal.pydantic import HAS_PYDANTIC_V2
25
-
26
- if HAS_PYDANTIC_V2:
27
- from pydantic.v1 import BaseModel, HttpUrl, SecretBytes, SecretStr, ValidationError
28
- else:
29
- from pydantic import BaseModel, HttpUrl, SecretBytes, SecretStr, ValidationError
30
-
31
- 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
32
42
 
33
43
  import prefect
34
44
  import prefect.exceptions
35
- from prefect.blocks.fields import SecretDict
36
45
  from prefect.client.schemas import (
37
46
  DEFAULT_BLOCK_SCHEMA_VERSION,
38
47
  BlockDocument,
@@ -41,26 +50,27 @@ from prefect.client.schemas import (
41
50
  BlockTypeUpdate,
42
51
  )
43
52
  from prefect.client.utilities import inject_client
44
- from prefect.events.instrument import (
45
- ResourceTuple,
46
- emit_instance_method_called_event,
47
- instrument_instance_method_call,
48
- instrument_method_calls_on_class_instances,
49
- )
53
+ from prefect.events import emit_event
50
54
  from prefect.logging.loggers import disable_logger
55
+ from prefect.types import SecretDict
51
56
  from prefect.utilities.asyncutils import sync_compatible
52
- from prefect.utilities.collections import listrepr, remove_nested_keys
57
+ from prefect.utilities.collections import listrepr, remove_nested_keys, visit_collection
53
58
  from prefect.utilities.dispatch import lookup_type, register_base_type
54
59
  from prefect.utilities.hashing import hash_objects
55
60
  from prefect.utilities.importtools import to_qualified_name
61
+ from prefect.utilities.pydantic import handle_secret_render
56
62
  from prefect.utilities.slugify import slugify
57
63
 
58
64
  if TYPE_CHECKING:
65
+ from pydantic.main import IncEx
66
+
59
67
  from prefect.client.orchestration import PrefectClient
60
68
 
61
69
  R = TypeVar("R")
62
70
  P = ParamSpec("P")
63
71
 
72
+ ResourceTuple = Tuple[Dict[str, Any], List[Dict[str, Any]]]
73
+
64
74
 
65
75
  def block_schema_to_key(schema: BlockSchema) -> str:
66
76
  """
@@ -123,7 +133,9 @@ def _is_subclass(cls, parent_cls) -> bool:
123
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
141
  secrets list, supporting nested Union / Dict / Tuple / List / BaseModel fields.
@@ -134,18 +146,24 @@ def _collect_secret_fields(name: str, type_: Type, secrets: List[str]) -> None:
134
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,60 +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
-
245
- def collect_block_schema_references(
246
- field_name: str, annotation: type
247
- ) -> None:
248
- """Walk through the annotation and collect block schemas for any nested blocks."""
249
- if Block.is_block_class(annotation):
250
- if isinstance(refs.get(field_name), list):
251
- refs[field_name].append(
252
- annotation._to_block_schema_reference_dict()
253
- )
254
- elif isinstance(refs.get(field_name), dict):
255
- refs[field_name] = [
256
- refs[field_name],
257
- annotation._to_block_schema_reference_dict(),
258
- ]
259
- else:
260
- refs[field_name] = annotation._to_block_schema_reference_dict()
261
- if get_origin(annotation) in (Union, list, tuple, dict):
262
- for type_ in get_args(annotation):
263
- collect_block_schema_references(field_name, type_)
264
-
265
- for field in model.__fields__.values():
266
- collect_block_schema_references(field.name, field.type_)
278
+ model_config = ConfigDict(
279
+ extra="allow",
280
+ json_schema_extra=schema_extra,
281
+ )
267
282
 
268
283
  def __init__(self, *args, **kwargs):
269
284
  super().__init__(*args, **kwargs)
@@ -274,7 +289,7 @@ class Block(BaseModel, ABC):
274
289
 
275
290
  def __repr_args__(self):
276
291
  repr_args = super().__repr_args__()
277
- data_keys = self.schema()["properties"].keys()
292
+ data_keys = self.model_json_schema()["properties"].keys()
278
293
  return [
279
294
  (key, value) for key, value in repr_args if key is None or key in data_keys
280
295
  ]
@@ -288,25 +303,26 @@ class Block(BaseModel, ABC):
288
303
  # Attribute to customize the name of the block type created
289
304
  # when the block is registered with the API. If not set, block
290
305
  # type name will default to the class name.
291
- _block_type_name: Optional[str] = None
292
- _block_type_slug: Optional[str] = None
306
+ _block_type_name: ClassVar[Optional[str]] = None
307
+ _block_type_slug: ClassVar[Optional[str]] = None
293
308
 
294
309
  # Attributes used to set properties on a block type when registered
295
310
  # with the API.
296
- _logo_url: Optional[HttpUrl] = None
297
- _documentation_url: Optional[HttpUrl] = None
298
- _description: Optional[str] = None
299
- _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
300
319
 
301
320
  # -- private instance variables
302
321
  # these are set when blocks are loaded from the API
303
- _block_type_id: Optional[UUID] = None
304
- _block_schema_id: Optional[UUID] = None
305
- _block_schema_capabilities: Optional[List[str]] = None
306
- _block_schema_version: Optional[str] = None
307
- _block_document_id: Optional[UUID] = None
308
- _block_document_name: Optional[str] = None
309
- _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)
310
326
 
311
327
  # Exclude `save` as it uses the `sync_compatible` decorator and needs to be
312
328
  # decorated directly.
@@ -318,6 +334,31 @@ class Block(BaseModel, ABC):
318
334
  return None # The base class is abstract
319
335
  return block_schema_to_key(cls._to_block_schema())
320
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
+
321
362
  @classmethod
322
363
  def get_block_type_name(cls):
323
364
  return cls._block_type_name or cls.__name__
@@ -383,7 +424,9 @@ class Block(BaseModel, ABC):
383
424
  str: The calculated checksum prefixed with the hashing algorithm used.
384
425
  """
385
426
  block_schema_fields = (
386
- 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
387
430
  )
388
431
  fields_for_checksum = remove_nested_keys(["secret_fields"], block_schema_fields)
389
432
  if fields_for_checksum.get("definitions"):
@@ -408,6 +451,7 @@ class Block(BaseModel, ABC):
408
451
  block_schema_id: Optional[UUID] = None,
409
452
  block_type_id: Optional[UUID] = None,
410
453
  is_anonymous: Optional[bool] = None,
454
+ include_secrets: bool = False,
411
455
  ) -> BlockDocument:
412
456
  """
413
457
  Creates the corresponding block document based on the data stored in a block.
@@ -445,10 +489,14 @@ class Block(BaseModel, ABC):
445
489
  # The keys passed to `include` must NOT be aliases, else some items will be missed
446
490
  # i.e. must do `self.schema_` vs `self.schema` to get a `schema_ = Field(alias="schema")`
447
491
  # reported from https://github.com/PrefectHQ/prefect-dbt/issues/54
448
- data_keys = self.schema(by_alias=False)["properties"].keys()
492
+ data_keys = self.model_json_schema(by_alias=False)["properties"].keys()
449
493
 
450
494
  # `block_document_data`` must return the aliased version for it to show in the UI
451
- 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
+ )
452
500
 
453
501
  # Iterate through and find blocks that already have saved block documents to
454
502
  # create references to those saved block documents.
@@ -488,7 +536,7 @@ class Block(BaseModel, ABC):
488
536
  Returns:
489
537
  BlockSchema: The corresponding block schema.
490
538
  """
491
- fields = cls.schema()
539
+ fields = cls.model_json_schema()
492
540
  return BlockSchema(
493
541
  id=cls._block_schema_id if cls._block_schema_id is not None else uuid4(),
494
542
  checksum=cls._calculate_schema_checksum(),
@@ -543,7 +591,7 @@ class Block(BaseModel, ABC):
543
591
  code example from the class docstring if an override is not provided.
544
592
  """
545
593
  code_example = (
546
- 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
547
595
  )
548
596
  # If no code example override has been provided, attempt to find a examples
549
597
  # section or an admonition with the annotation "example" and use that as the
@@ -610,7 +658,7 @@ class Block(BaseModel, ABC):
610
658
  )
611
659
 
612
660
  @classmethod
613
- def _from_block_document(cls, block_document: BlockDocument):
661
+ def _from_block_document(cls, block_document: BlockDocument) -> Self:
614
662
  """
615
663
  Instantiates a block from a given block document. The corresponding block class
616
664
  will be looked up in the block registry based on the corresponding block schema
@@ -638,9 +686,7 @@ class Block(BaseModel, ABC):
638
686
  else cls.get_block_class_from_schema(block_document.block_schema)
639
687
  )
640
688
 
641
- block_cls = instrument_method_calls_on_class_instances(block_cls)
642
-
643
- block = block_cls.parse_obj(block_document.data)
689
+ block = block_cls.model_validate(block_document.data)
644
690
  block._block_document_id = block_document.id
645
691
  block.__class__._block_schema_id = block_document.block_schema_id
646
692
  block.__class__._block_type_id = block_document.block_type_id
@@ -650,13 +696,11 @@ class Block(BaseModel, ABC):
650
696
  block_document.block_document_references
651
697
  )
652
698
 
653
- # Due to the way blocks are loaded we can't directly instrument the
654
- # `load` method and have the data be about the block document. Instead
655
- # this will emit a proxy event for the load method so that block
656
- # document data can be included instead of the event being about an
657
- # 'anonymous' block.
658
-
659
- 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)
660
704
 
661
705
  return block
662
706
 
@@ -735,7 +779,7 @@ class Block(BaseModel, ABC):
735
779
  async def _get_block_document(
736
780
  cls,
737
781
  name: str,
738
- client: "PrefectClient" = None,
782
+ client: Optional["PrefectClient"] = None,
739
783
  ):
740
784
  if cls.__name__ == "Block":
741
785
  block_type_slug, block_document_name = name.split("/", 1)
@@ -755,6 +799,33 @@ class Block(BaseModel, ABC):
755
799
 
756
800
  return block_document, block_document_name
757
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
+
758
829
  @classmethod
759
830
  @sync_compatible
760
831
  @inject_client
@@ -762,8 +833,8 @@ class Block(BaseModel, ABC):
762
833
  cls,
763
834
  name: str,
764
835
  validate: bool = True,
765
- client: "PrefectClient" = None,
766
- ):
836
+ client: Optional["PrefectClient"] = None,
837
+ ) -> "Self":
767
838
  """
768
839
  Retrieves data from the block document with the given name for the block type
769
840
  that corresponds with the current class and returns an instantiated version of
@@ -839,8 +910,108 @@ class Block(BaseModel, ABC):
839
910
  loaded_block.save("my-custom-message", overwrite=True)
840
911
  ```
841
912
  """
842
- 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`.
843
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.
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
+ """
844
1015
  try:
845
1016
  return cls._from_block_document(block_document)
846
1017
  except ValidationError as e:
@@ -848,19 +1019,19 @@ class Block(BaseModel, ABC):
848
1019
  missing_fields = tuple(err["loc"][0] for err in e.errors())
849
1020
  missing_block_data = {field: None for field in missing_fields}
850
1021
  warnings.warn(
851
- f"Could not fully load {block_document_name!r} of block type"
852
- 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"
853
1024
  " required fields were added to the schema for"
854
1025
  f" {cls.__name__!r} that did not exist on the class when this block"
855
1026
  " was last saved. Please specify values for new field(s):"
856
1027
  f" {listrepr(missing_fields)}, then run"
857
- f' `{cls.__name__}.save("{block_document_name}", overwrite=True)`,'
1028
+ f' `{cls.__name__}.save("{block_document.name}", overwrite=True)`,'
858
1029
  " and load this block again before attempting to use it."
859
1030
  )
860
- return cls.construct(**block_document.data, **missing_block_data)
1031
+ return cls.model_construct(**block_document.data, **missing_block_data)
861
1032
  raise RuntimeError(
862
- f"Unable to load {block_document_name!r} of block type"
863
- 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"
864
1035
  " validation, try loading again with `validate=False`."
865
1036
  ) from e
866
1037
 
@@ -868,10 +1039,22 @@ class Block(BaseModel, ABC):
868
1039
  def is_block_class(block) -> bool:
869
1040
  return _is_subclass(block, Block)
870
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
+
871
1054
  @classmethod
872
1055
  @sync_compatible
873
1056
  @inject_client
874
- async def register_type_and_schema(cls, client: "PrefectClient" = None):
1057
+ async def register_type_and_schema(cls, client: Optional["PrefectClient"] = None):
875
1058
  """
876
1059
  Makes block available for configuration with current Prefect API.
877
1060
  Recursively registers all nested blocks. Registration is idempotent.
@@ -900,14 +1083,16 @@ class Block(BaseModel, ABC):
900
1083
  for inner_annotation in get_args(annotation):
901
1084
  await register_blocks_in_annotation(inner_annotation)
902
1085
 
903
- for field in cls.__fields__.values():
1086
+ for field in cls.model_fields.values():
904
1087
  await register_blocks_in_annotation(field.annotation)
905
1088
 
906
1089
  try:
907
1090
  block_type = await client.read_block_type_by_slug(
908
1091
  slug=cls.get_block_type_slug()
909
1092
  )
1093
+
910
1094
  cls._block_type_id = block_type.id
1095
+
911
1096
  local_block_type = cls._to_block_type()
912
1097
  if _should_update_block_type(
913
1098
  local_block_type=local_block_type, server_block_type=block_type
@@ -937,7 +1122,7 @@ class Block(BaseModel, ABC):
937
1122
  name: Optional[str] = None,
938
1123
  is_anonymous: bool = False,
939
1124
  overwrite: bool = False,
940
- client: "PrefectClient" = None,
1125
+ client: Optional["PrefectClient"] = None,
941
1126
  ):
942
1127
  """
943
1128
  Saves the values of a block as a block document with an option to save as an
@@ -985,7 +1170,9 @@ class Block(BaseModel, ABC):
985
1170
  block_document_id = existing_block_document.id
986
1171
  await client.update_block_document(
987
1172
  block_document_id=block_document_id,
988
- block_document=self._to_block_document(name=name),
1173
+ block_document=self._to_block_document(
1174
+ name=name, include_secrets=True
1175
+ ),
989
1176
  )
990
1177
  block_document = await client.read_block_document(
991
1178
  block_document_id=block_document_id
@@ -1003,12 +1190,11 @@ class Block(BaseModel, ABC):
1003
1190
  return self._block_document_id
1004
1191
 
1005
1192
  @sync_compatible
1006
- @instrument_instance_method_call
1007
1193
  async def save(
1008
1194
  self,
1009
1195
  name: Optional[str] = None,
1010
1196
  overwrite: bool = False,
1011
- client: "PrefectClient" = None,
1197
+ client: Optional["PrefectClient"] = None,
1012
1198
  ):
1013
1199
  """
1014
1200
  Saves the values of a block as a block document.
@@ -1030,25 +1216,12 @@ class Block(BaseModel, ABC):
1030
1216
  async def delete(
1031
1217
  cls,
1032
1218
  name: str,
1033
- client: "PrefectClient" = None,
1219
+ client: Optional["PrefectClient"] = None,
1034
1220
  ):
1035
1221
  block_document, block_document_name = await cls._get_block_document(name)
1036
1222
 
1037
1223
  await client.delete_block_document(block_document.id)
1038
1224
 
1039
- def _iter(self, *, include=None, exclude=None, **kwargs):
1040
- # Injects the `block_type_slug` into serialized payloads for dispatch
1041
- for key_value in super()._iter(include=include, exclude=exclude, **kwargs):
1042
- yield key_value
1043
-
1044
- # Respect inclusion and exclusion still
1045
- if include and "block_type_slug" not in include:
1046
- return
1047
- if exclude and "block_type_slug" in exclude:
1048
- return
1049
-
1050
- yield "block_type_slug", self.get_block_type_slug()
1051
-
1052
1225
  def __new__(cls: Type[Self], **kwargs) -> Self:
1053
1226
  """
1054
1227
  Create an instance of the Block subclass type if a `block_type_slug` is
@@ -1057,21 +1230,9 @@ class Block(BaseModel, ABC):
1057
1230
  block_type_slug = kwargs.pop("block_type_slug", None)
1058
1231
  if block_type_slug:
1059
1232
  subcls = lookup_type(cls, dispatch_key=block_type_slug)
1060
- m = super().__new__(subcls)
1061
- # NOTE: This is a workaround for an obscure issue where copied models were
1062
- # missing attributes. This pattern is from Pydantic's
1063
- # `BaseModel._copy_and_set_values`.
1064
- # The issue this fixes could not be reproduced in unit tests that
1065
- # directly targeted dispatch handling and was only observed when
1066
- # copying then saving infrastructure blocks on deployment models.
1067
- object.__setattr__(m, "__dict__", kwargs)
1068
- object.__setattr__(m, "__fields_set__", set(kwargs.keys()))
1069
- return m
1233
+ return super().__new__(subcls)
1070
1234
  else:
1071
- m = super().__new__(cls)
1072
- object.__setattr__(m, "__dict__", kwargs)
1073
- object.__setattr__(m, "__fields_set__", set(kwargs.keys()))
1074
- return m
1235
+ return super().__new__(cls)
1075
1236
 
1076
1237
  def get_block_placeholder(self) -> str:
1077
1238
  """
@@ -1094,3 +1255,94 @@ class Block(BaseModel, ABC):
1094
1255
  )
1095
1256
 
1096
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