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
@@ -8,12 +8,13 @@ from typing import (
8
8
  Dict,
9
9
  Iterable,
10
10
  List,
11
- NoReturn,
11
+ Literal,
12
12
  Optional,
13
13
  Set,
14
14
  Tuple,
15
15
  TypeVar,
16
16
  Union,
17
+ overload,
17
18
  )
18
19
  from uuid import UUID, uuid4
19
20
 
@@ -21,35 +22,20 @@ import certifi
21
22
  import httpcore
22
23
  import httpx
23
24
  import pendulum
24
- from typing_extensions import ParamSpec
25
-
26
- from prefect._internal.compatibility.deprecated import (
27
- handle_deprecated_infra_overrides_parameter,
28
- )
29
- from prefect._internal.pydantic import HAS_PYDANTIC_V2
30
- from prefect.client.schemas import sorting
31
- from prefect.events import filters
32
- from prefect.settings import (
33
- PREFECT_API_SERVICES_TRIGGERS_ENABLED,
34
- PREFECT_EXPERIMENTAL_EVENTS,
35
- )
36
-
37
- if HAS_PYDANTIC_V2:
38
- import pydantic.v1 as pydantic
39
- else:
40
- import pydantic
41
-
25
+ import pydantic
42
26
  from asgi_lifespan import LifespanManager
43
- from prefect._vendor.starlette import status
27
+ from starlette import status
28
+ from typing_extensions import ParamSpec
44
29
 
45
30
  import prefect
46
31
  import prefect.exceptions
47
32
  import prefect.settings
48
33
  import prefect.states
49
34
  from prefect.client.constants import SERVER_API_VERSION
50
- from prefect.client.schemas import FlowRun, OrchestrationResult, TaskRun
35
+ from prefect.client.schemas import FlowRun, OrchestrationResult, TaskRun, sorting
51
36
  from prefect.client.schemas.actions import (
52
37
  ArtifactCreate,
38
+ ArtifactUpdate,
53
39
  BlockDocumentCreate,
54
40
  BlockDocumentUpdate,
55
41
  BlockSchemaCreate,
@@ -100,7 +86,6 @@ from prefect.client.schemas.objects import (
100
86
  BlockType,
101
87
  ConcurrencyLimit,
102
88
  Constant,
103
- Deployment,
104
89
  DeploymentSchedule,
105
90
  Flow,
106
91
  FlowRunInput,
@@ -133,7 +118,7 @@ from prefect.client.schemas.sorting import (
133
118
  LogSort,
134
119
  TaskRunSort,
135
120
  )
136
- from prefect.deprecated.data_documents import DataDocument
121
+ from prefect.events import filters
137
122
  from prefect.events.schemas.automations import Automation, AutomationCore
138
123
  from prefect.logging import get_logger
139
124
  from prefect.settings import (
@@ -146,9 +131,9 @@ from prefect.settings import (
146
131
  PREFECT_API_URL,
147
132
  PREFECT_CLIENT_CSRF_SUPPORT_ENABLED,
148
133
  PREFECT_CLOUD_API_URL,
134
+ PREFECT_SERVER_ALLOW_EPHEMERAL_MODE,
149
135
  PREFECT_UNIT_TEST_MODE,
150
136
  )
151
- from prefect.utilities.collections import AutoEnum
152
137
 
153
138
  if TYPE_CHECKING:
154
139
  from prefect.flows import Flow as FlowObject
@@ -158,7 +143,7 @@ from prefect.client.base import (
158
143
  ASGIApp,
159
144
  PrefectHttpxAsyncClient,
160
145
  PrefectHttpxSyncClient,
161
- PrefectHttpxSyncEphemeralClient,
146
+ ServerType,
162
147
  app_lifespan_context,
163
148
  )
164
149
 
@@ -166,21 +151,23 @@ P = ParamSpec("P")
166
151
  R = TypeVar("R")
167
152
 
168
153
 
169
- class ServerType(AutoEnum):
170
- EPHEMERAL = AutoEnum.auto()
171
- SERVER = AutoEnum.auto()
172
- CLOUD = AutoEnum.auto()
154
+ @overload
155
+ def get_client(
156
+ httpx_settings: Optional[Dict[str, Any]] = None, sync_client: Literal[False] = False
157
+ ) -> "PrefectClient":
158
+ ...
173
159
 
174
- def supports_automations(self) -> bool:
175
- if self == ServerType.CLOUD:
176
- return True
177
160
 
178
- return PREFECT_EXPERIMENTAL_EVENTS and PREFECT_API_SERVICES_TRIGGERS_ENABLED
161
+ @overload
162
+ def get_client(
163
+ httpx_settings: Optional[Dict[str, Any]] = None, sync_client: Literal[True] = True
164
+ ) -> "SyncPrefectClient":
165
+ ...
179
166
 
180
167
 
181
168
  def get_client(
182
169
  httpx_settings: Optional[Dict[str, Any]] = None, sync_client: bool = False
183
- ) -> Union["PrefectClient", "SyncPrefectClient"]:
170
+ ):
184
171
  """
185
172
  Retrieve a HTTP client for communicating with the Prefect REST API.
186
173
 
@@ -198,26 +185,59 @@ def get_client(
198
185
  client.hello()
199
186
  ```
200
187
  """
201
- ctx = prefect.context.get_settings_context()
188
+ import prefect.context
189
+
190
+ # try to load clients from a client context, if possible
191
+ # only load clients that match the provided config / loop
192
+ try:
193
+ loop = asyncio.get_running_loop()
194
+ except RuntimeError:
195
+ loop = None
196
+
197
+ if sync_client:
198
+ if client_ctx := prefect.context.SyncClientContext.get():
199
+ if client_ctx.client and client_ctx._httpx_settings == httpx_settings:
200
+ return client_ctx.client
201
+ else:
202
+ if client_ctx := prefect.context.AsyncClientContext.get():
203
+ if (
204
+ client_ctx.client
205
+ and client_ctx._httpx_settings == httpx_settings
206
+ and loop in (client_ctx.client._loop, None)
207
+ ):
208
+ return client_ctx.client
209
+
202
210
  api = PREFECT_API_URL.value()
211
+ server_type = None
203
212
 
204
- if not api:
213
+ if not api and PREFECT_SERVER_ALLOW_EPHEMERAL_MODE:
205
214
  # create an ephemeral API if none was provided
206
- from prefect.server.api.server import create_app
215
+ from prefect.server.api.server import SubprocessASGIServer
207
216
 
208
- api = create_app(ctx.settings, ephemeral=True)
217
+ server = SubprocessASGIServer()
218
+ server.start()
219
+ assert server.server_process is not None, "Server process did not start"
220
+
221
+ api = server.api_url
222
+ server_type = ServerType.EPHEMERAL
223
+ elif not api and not PREFECT_SERVER_ALLOW_EPHEMERAL_MODE:
224
+ raise ValueError(
225
+ "No Prefect API URL provided. Please set PREFECT_API_URL to the address of a running Prefect server."
226
+ )
209
227
 
210
228
  if sync_client:
211
229
  return SyncPrefectClient(
212
230
  api,
213
231
  api_key=PREFECT_API_KEY.value(),
214
232
  httpx_settings=httpx_settings,
233
+ server_type=server_type,
215
234
  )
216
235
  else:
217
236
  return PrefectClient(
218
237
  api,
219
238
  api_key=PREFECT_API_KEY.value(),
220
239
  httpx_settings=httpx_settings,
240
+ server_type=server_type,
221
241
  )
222
242
 
223
243
 
@@ -251,9 +271,10 @@ class PrefectClient:
251
271
  self,
252
272
  api: Union[str, ASGIApp],
253
273
  *,
254
- api_key: str = None,
255
- api_version: str = None,
274
+ api_key: Optional[str] = None,
275
+ api_version: Optional[str] = None,
256
276
  httpx_settings: Optional[Dict[str, Any]] = None,
277
+ server_type: Optional[ServerType] = None,
257
278
  ) -> None:
258
279
  httpx_settings = httpx_settings.copy() if httpx_settings else {}
259
280
  httpx_settings.setdefault("headers", {})
@@ -273,6 +294,7 @@ class PrefectClient:
273
294
  httpx_settings["headers"].setdefault("Authorization", f"Bearer {api_key}")
274
295
 
275
296
  # Context management
297
+ self._context_stack: int = 0
276
298
  self._exit_stack = AsyncExitStack()
277
299
  self._ephemeral_app: Optional[ASGIApp] = None
278
300
  self.manage_lifespan = True
@@ -315,11 +337,14 @@ class PrefectClient:
315
337
  # client will use a standard HTTP/1.1 connection instead.
316
338
  httpx_settings.setdefault("http2", PREFECT_API_ENABLE_HTTP2.value())
317
339
 
318
- self.server_type = (
319
- ServerType.CLOUD
320
- if api.startswith(PREFECT_CLOUD_API_URL.value())
321
- else ServerType.SERVER
322
- )
340
+ if server_type:
341
+ self.server_type = server_type
342
+ else:
343
+ self.server_type = (
344
+ ServerType.CLOUD
345
+ if api.startswith(PREFECT_CLOUD_API_URL.value())
346
+ else ServerType.SERVER
347
+ )
323
348
 
324
349
  # Connect to an in-process application
325
350
  elif isinstance(api, ASGIApp):
@@ -453,7 +478,7 @@ class PrefectClient:
453
478
  """
454
479
  flow_data = FlowCreate(name=flow_name)
455
480
  response = await self._client.post(
456
- "/flows/", json=flow_data.dict(json_compatible=True)
481
+ "/flows/", json=flow_data.model_dump(mode="json")
457
482
  )
458
483
 
459
484
  flow_id = response.json().get("id")
@@ -474,7 +499,7 @@ class PrefectClient:
474
499
  a [Flow model][prefect.client.schemas.objects.Flow] representation of the flow
475
500
  """
476
501
  response = await self._client.get(f"/flows/{flow_id}")
477
- return Flow.parse_obj(response.json())
502
+ return Flow.model_validate(response.json())
478
503
 
479
504
  async def read_flows(
480
505
  self,
@@ -486,7 +511,7 @@ class PrefectClient:
486
511
  work_pool_filter: WorkPoolFilter = None,
487
512
  work_queue_filter: WorkQueueFilter = None,
488
513
  sort: FlowSort = None,
489
- limit: int = None,
514
+ limit: Optional[int] = None,
490
515
  offset: int = 0,
491
516
  ) -> List[Flow]:
492
517
  """
@@ -508,29 +533,23 @@ class PrefectClient:
508
533
  a list of Flow model representations of the flows
509
534
  """
510
535
  body = {
511
- "flows": flow_filter.dict(json_compatible=True) if flow_filter else None,
536
+ "flows": flow_filter.model_dump(mode="json") if flow_filter else None,
512
537
  "flow_runs": (
513
- flow_run_filter.dict(json_compatible=True, exclude_unset=True)
538
+ flow_run_filter.model_dump(mode="json", exclude_unset=True)
514
539
  if flow_run_filter
515
540
  else None
516
541
  ),
517
542
  "task_runs": (
518
- task_run_filter.dict(json_compatible=True) if task_run_filter else None
543
+ task_run_filter.model_dump(mode="json") if task_run_filter else None
519
544
  ),
520
545
  "deployments": (
521
- deployment_filter.dict(json_compatible=True)
522
- if deployment_filter
523
- else None
546
+ deployment_filter.model_dump(mode="json") if deployment_filter else None
524
547
  ),
525
548
  "work_pools": (
526
- work_pool_filter.dict(json_compatible=True)
527
- if work_pool_filter
528
- else None
549
+ work_pool_filter.model_dump(mode="json") if work_pool_filter else None
529
550
  ),
530
551
  "work_queues": (
531
- work_queue_filter.dict(json_compatible=True)
532
- if work_queue_filter
533
- else None
552
+ work_queue_filter.model_dump(mode="json") if work_queue_filter else None
534
553
  ),
535
554
  "sort": sort,
536
555
  "limit": limit,
@@ -538,7 +557,7 @@ class PrefectClient:
538
557
  }
539
558
 
540
559
  response = await self._client.post("/flows/filter", json=body)
541
- return pydantic.parse_obj_as(List[Flow], response.json())
560
+ return pydantic.TypeAdapter(List[Flow]).validate_python(response.json())
542
561
 
543
562
  async def read_flow_by_name(
544
563
  self,
@@ -554,7 +573,7 @@ class PrefectClient:
554
573
  a fully hydrated Flow model
555
574
  """
556
575
  response = await self._client.get(f"/flows/name/{flow_name}")
557
- return Flow.parse_obj(response.json())
576
+ return Flow.model_validate(response.json())
558
577
 
559
578
  async def create_flow_run_from_deployment(
560
579
  self,
@@ -562,12 +581,12 @@ class PrefectClient:
562
581
  *,
563
582
  parameters: Optional[Dict[str, Any]] = None,
564
583
  context: Optional[Dict[str, Any]] = None,
565
- state: prefect.states.State = None,
566
- name: str = None,
567
- tags: Iterable[str] = None,
568
- idempotency_key: str = None,
569
- parent_task_run_id: UUID = None,
570
- work_queue_name: str = None,
584
+ state: Optional[prefect.states.State] = None,
585
+ name: Optional[str] = None,
586
+ tags: Optional[Iterable[str]] = None,
587
+ idempotency_key: Optional[str] = None,
588
+ parent_task_run_id: Optional[UUID] = None,
589
+ work_queue_name: Optional[str] = None,
571
590
  job_variables: Optional[Dict[str, Any]] = None,
572
591
  ) -> FlowRun:
573
592
  """
@@ -622,9 +641,9 @@ class PrefectClient:
622
641
 
623
642
  response = await self._client.post(
624
643
  f"/deployments/{deployment_id}/create_flow_run",
625
- json=flow_run_create.dict(json_compatible=True, exclude_unset=True),
644
+ json=flow_run_create.model_dump(mode="json", exclude_unset=True),
626
645
  )
627
- return FlowRun.parse_obj(response.json())
646
+ return FlowRun.model_validate(response.json())
628
647
 
629
648
  async def create_flow_run(
630
649
  self,
@@ -680,9 +699,9 @@ class PrefectClient:
680
699
  ),
681
700
  )
682
701
 
683
- flow_run_create_json = flow_run_create.dict(json_compatible=True)
702
+ flow_run_create_json = flow_run_create.model_dump(mode="json")
684
703
  response = await self._client.post("/flow_runs/", json=flow_run_create_json)
685
- flow_run = FlowRun.parse_obj(response.json())
704
+ flow_run = FlowRun.model_validate(response.json())
686
705
 
687
706
  # Restore the parameters to the local objects to retain expectations about
688
707
  # Python objects
@@ -740,7 +759,7 @@ class PrefectClient:
740
759
 
741
760
  return await self._client.patch(
742
761
  f"/flow_runs/{flow_run_id}",
743
- json=flow_run_data.dict(json_compatible=True, exclude_unset=True),
762
+ json=flow_run_data.model_dump(mode="json", exclude_unset=True),
744
763
  )
745
764
 
746
765
  async def delete_flow_run(
@@ -790,7 +809,7 @@ class PrefectClient:
790
809
  )
791
810
  response = await self._client.post(
792
811
  "/concurrency_limits/",
793
- json=concurrency_limit_create.dict(json_compatible=True),
812
+ json=concurrency_limit_create.model_dump(mode="json"),
794
813
  )
795
814
 
796
815
  concurrency_limit_id = response.json().get("id")
@@ -832,7 +851,7 @@ class PrefectClient:
832
851
  if not concurrency_limit_id:
833
852
  raise httpx.RequestError(f"Malformed response: {response}")
834
853
 
835
- concurrency_limit = ConcurrencyLimit.parse_obj(response.json())
854
+ concurrency_limit = ConcurrencyLimit.model_validate(response.json())
836
855
  return concurrency_limit
837
856
 
838
857
  async def read_concurrency_limits(
@@ -857,7 +876,9 @@ class PrefectClient:
857
876
  }
858
877
 
859
878
  response = await self._client.post("/concurrency_limits/filter", json=body)
860
- return pydantic.parse_obj_as(List[ConcurrencyLimit], response.json())
879
+ return pydantic.TypeAdapter(List[ConcurrencyLimit]).validate_python(
880
+ response.json()
881
+ )
861
882
 
862
883
  async def reset_concurrency_limit_by_tag(
863
884
  self,
@@ -918,6 +939,57 @@ class PrefectClient:
918
939
  else:
919
940
  raise
920
941
 
942
+ async def increment_v1_concurrency_slots(
943
+ self,
944
+ names: List[str],
945
+ task_run_id: UUID,
946
+ ) -> httpx.Response:
947
+ """
948
+ Increment concurrency limit slots for the specified limits.
949
+
950
+ Args:
951
+ names (List[str]): A list of limit names for which to increment limits.
952
+ task_run_id (UUID): The task run ID incrementing the limits.
953
+ """
954
+ data = {
955
+ "names": names,
956
+ "task_run_id": str(task_run_id),
957
+ }
958
+
959
+ return await self._client.post(
960
+ "/concurrency_limits/increment",
961
+ json=data,
962
+ )
963
+
964
+ async def decrement_v1_concurrency_slots(
965
+ self,
966
+ names: List[str],
967
+ task_run_id: UUID,
968
+ occupancy_seconds: float,
969
+ ) -> httpx.Response:
970
+ """
971
+ Decrement concurrency limit slots for the specified limits.
972
+
973
+ Args:
974
+ names (List[str]): A list of limit names to decrement.
975
+ task_run_id (UUID): The task run ID that incremented the limits.
976
+ occupancy_seconds (float): The duration in seconds that the limits
977
+ were held.
978
+
979
+ Returns:
980
+ httpx.Response: The HTTP response from the server.
981
+ """
982
+ data = {
983
+ "names": names,
984
+ "task_run_id": str(task_run_id),
985
+ "occupancy_seconds": occupancy_seconds,
986
+ }
987
+
988
+ return await self._client.post(
989
+ "/concurrency_limits/decrement",
990
+ json=data,
991
+ )
992
+
921
993
  async def create_work_queue(
922
994
  self,
923
995
  name: str,
@@ -969,7 +1041,7 @@ class PrefectClient:
969
1041
  if priority is not None:
970
1042
  create_model.priority = priority
971
1043
 
972
- data = create_model.dict(json_compatible=True)
1044
+ data = create_model.model_dump(mode="json")
973
1045
  try:
974
1046
  if work_pool_name is not None:
975
1047
  response = await self._client.post(
@@ -984,7 +1056,7 @@ class PrefectClient:
984
1056
  raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
985
1057
  else:
986
1058
  raise
987
- return WorkQueue.parse_obj(response.json())
1059
+ return WorkQueue.model_validate(response.json())
988
1060
 
989
1061
  async def read_work_queue_by_name(
990
1062
  self,
@@ -1019,7 +1091,7 @@ class PrefectClient:
1019
1091
  else:
1020
1092
  raise
1021
1093
 
1022
- return WorkQueue.parse_obj(response.json())
1094
+ return WorkQueue.model_validate(response.json())
1023
1095
 
1024
1096
  async def update_work_queue(self, id: UUID, **kwargs):
1025
1097
  """
@@ -1038,7 +1110,7 @@ class PrefectClient:
1038
1110
  if not kwargs:
1039
1111
  raise ValueError("No fields provided to update.")
1040
1112
 
1041
- data = WorkQueueUpdate(**kwargs).dict(json_compatible=True, exclude_unset=True)
1113
+ data = WorkQueueUpdate(**kwargs).model_dump(mode="json", exclude_unset=True)
1042
1114
  try:
1043
1115
  await self._client.patch(f"/work_queues/{id}", json=data)
1044
1116
  except httpx.HTTPStatusError as e:
@@ -1085,7 +1157,7 @@ class PrefectClient:
1085
1157
  raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
1086
1158
  else:
1087
1159
  raise
1088
- return pydantic.parse_obj_as(List[FlowRun], response.json())
1160
+ return pydantic.TypeAdapter(List[FlowRun]).validate_python(response.json())
1089
1161
 
1090
1162
  async def read_work_queue(
1091
1163
  self,
@@ -1111,7 +1183,7 @@ class PrefectClient:
1111
1183
  raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
1112
1184
  else:
1113
1185
  raise
1114
- return WorkQueue.parse_obj(response.json())
1186
+ return WorkQueue.model_validate(response.json())
1115
1187
 
1116
1188
  async def read_work_queue_status(
1117
1189
  self,
@@ -1137,7 +1209,7 @@ class PrefectClient:
1137
1209
  raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
1138
1210
  else:
1139
1211
  raise
1140
- return WorkQueueStatusDetail.parse_obj(response.json())
1212
+ return WorkQueueStatusDetail.model_validate(response.json())
1141
1213
 
1142
1214
  async def match_work_queues(
1143
1215
  self,
@@ -1206,8 +1278,8 @@ class PrefectClient:
1206
1278
  try:
1207
1279
  response = await self._client.post(
1208
1280
  "/block_types/",
1209
- json=block_type.dict(
1210
- json_compatible=True, exclude_unset=True, exclude={"id"}
1281
+ json=block_type.model_dump(
1282
+ mode="json", exclude_unset=True, exclude={"id"}
1211
1283
  ),
1212
1284
  )
1213
1285
  except httpx.HTTPStatusError as e:
@@ -1215,7 +1287,7 @@ class PrefectClient:
1215
1287
  raise prefect.exceptions.ObjectAlreadyExists(http_exc=e) from e
1216
1288
  else:
1217
1289
  raise
1218
- return BlockType.parse_obj(response.json())
1290
+ return BlockType.model_validate(response.json())
1219
1291
 
1220
1292
  async def create_block_schema(self, block_schema: BlockSchemaCreate) -> BlockSchema:
1221
1293
  """
@@ -1224,8 +1296,8 @@ class PrefectClient:
1224
1296
  try:
1225
1297
  response = await self._client.post(
1226
1298
  "/block_schemas/",
1227
- json=block_schema.dict(
1228
- json_compatible=True,
1299
+ json=block_schema.model_dump(
1300
+ mode="json",
1229
1301
  exclude_unset=True,
1230
1302
  exclude={"id", "block_type", "checksum"},
1231
1303
  ),
@@ -1235,7 +1307,7 @@ class PrefectClient:
1235
1307
  raise prefect.exceptions.ObjectAlreadyExists(http_exc=e) from e
1236
1308
  else:
1237
1309
  raise
1238
- return BlockSchema.parse_obj(response.json())
1310
+ return BlockSchema.model_validate(response.json())
1239
1311
 
1240
1312
  async def create_block_document(
1241
1313
  self,
@@ -1252,32 +1324,24 @@ class PrefectClient:
1252
1324
  `SecretBytes` fields. Note Blocks may not work as expected if
1253
1325
  this is set to `False`.
1254
1326
  """
1255
- if isinstance(block_document, BlockDocument):
1256
- block_document = BlockDocumentCreate.parse_obj(
1257
- block_document.dict(
1258
- json_compatible=True,
1259
- include_secrets=include_secrets,
1260
- exclude_unset=True,
1261
- exclude={"id", "block_schema", "block_type"},
1262
- ),
1263
- )
1264
-
1327
+ block_document_data = block_document.model_dump(
1328
+ mode="json",
1329
+ exclude_unset=True,
1330
+ exclude={"id", "block_schema", "block_type"},
1331
+ context={"include_secrets": include_secrets},
1332
+ serialize_as_any=True,
1333
+ )
1265
1334
  try:
1266
1335
  response = await self._client.post(
1267
1336
  "/block_documents/",
1268
- json=block_document.dict(
1269
- json_compatible=True,
1270
- include_secrets=include_secrets,
1271
- exclude_unset=True,
1272
- exclude={"id", "block_schema", "block_type"},
1273
- ),
1337
+ json=block_document_data,
1274
1338
  )
1275
1339
  except httpx.HTTPStatusError as e:
1276
1340
  if e.response.status_code == status.HTTP_409_CONFLICT:
1277
1341
  raise prefect.exceptions.ObjectAlreadyExists(http_exc=e) from e
1278
1342
  else:
1279
1343
  raise
1280
- return BlockDocument.parse_obj(response.json())
1344
+ return BlockDocument.model_validate(response.json())
1281
1345
 
1282
1346
  async def update_block_document(
1283
1347
  self,
@@ -1290,11 +1354,10 @@ class PrefectClient:
1290
1354
  try:
1291
1355
  await self._client.patch(
1292
1356
  f"/block_documents/{block_document_id}",
1293
- json=block_document.dict(
1294
- json_compatible=True,
1357
+ json=block_document.model_dump(
1358
+ mode="json",
1295
1359
  exclude_unset=True,
1296
1360
  include={"data", "merge_existing_data", "block_schema_id"},
1297
- include_secrets=True,
1298
1361
  ),
1299
1362
  )
1300
1363
  except httpx.HTTPStatusError as e:
@@ -1326,7 +1389,7 @@ class PrefectClient:
1326
1389
  raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
1327
1390
  else:
1328
1391
  raise
1329
- return BlockType.parse_obj(response.json())
1392
+ return BlockType.model_validate(response.json())
1330
1393
 
1331
1394
  async def read_block_schema_by_checksum(
1332
1395
  self, checksum: str, version: Optional[str] = None
@@ -1344,7 +1407,7 @@ class PrefectClient:
1344
1407
  raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
1345
1408
  else:
1346
1409
  raise
1347
- return BlockSchema.parse_obj(response.json())
1410
+ return BlockSchema.model_validate(response.json())
1348
1411
 
1349
1412
  async def update_block_type(self, block_type_id: UUID, block_type: BlockTypeUpdate):
1350
1413
  """
@@ -1353,11 +1416,10 @@ class PrefectClient:
1353
1416
  try:
1354
1417
  await self._client.patch(
1355
1418
  f"/block_types/{block_type_id}",
1356
- json=block_type.dict(
1357
- json_compatible=True,
1419
+ json=block_type.model_dump(
1420
+ mode="json",
1358
1421
  exclude_unset=True,
1359
1422
  include=BlockTypeUpdate.updatable_fields(),
1360
- include_secrets=True,
1361
1423
  ),
1362
1424
  )
1363
1425
  except httpx.HTTPStatusError as e:
@@ -1396,7 +1458,7 @@ class PrefectClient:
1396
1458
  List of BlockTypes.
1397
1459
  """
1398
1460
  response = await self._client.post("/block_types/filter", json={})
1399
- return pydantic.parse_obj_as(List[BlockType], response.json())
1461
+ return pydantic.TypeAdapter(List[BlockType]).validate_python(response.json())
1400
1462
 
1401
1463
  async def read_block_schemas(self) -> List[BlockSchema]:
1402
1464
  """
@@ -1408,7 +1470,7 @@ class PrefectClient:
1408
1470
  A BlockSchema.
1409
1471
  """
1410
1472
  response = await self._client.post("/block_schemas/filter", json={})
1411
- return pydantic.parse_obj_as(List[BlockSchema], response.json())
1473
+ return pydantic.TypeAdapter(List[BlockSchema]).validate_python(response.json())
1412
1474
 
1413
1475
  async def get_most_recent_block_schema_for_block_type(
1414
1476
  self,
@@ -1436,7 +1498,9 @@ class PrefectClient:
1436
1498
  )
1437
1499
  except httpx.HTTPStatusError:
1438
1500
  raise
1439
- return BlockSchema.parse_obj(response.json()[0]) if response.json() else None
1501
+ return (
1502
+ BlockSchema.model_validate(response.json()[0]) if response.json() else None
1503
+ )
1440
1504
 
1441
1505
  async def read_block_document(
1442
1506
  self,
@@ -1474,7 +1538,7 @@ class PrefectClient:
1474
1538
  raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
1475
1539
  else:
1476
1540
  raise
1477
- return BlockDocument.parse_obj(response.json())
1541
+ return BlockDocument.model_validate(response.json())
1478
1542
 
1479
1543
  async def read_block_document_by_name(
1480
1544
  self,
@@ -1512,7 +1576,7 @@ class PrefectClient:
1512
1576
  raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
1513
1577
  else:
1514
1578
  raise
1515
- return BlockDocument.parse_obj(response.json())
1579
+ return BlockDocument.model_validate(response.json())
1516
1580
 
1517
1581
  async def read_block_documents(
1518
1582
  self,
@@ -1547,7 +1611,9 @@ class PrefectClient:
1547
1611
  include_secrets=include_secrets,
1548
1612
  ),
1549
1613
  )
1550
- return pydantic.parse_obj_as(List[BlockDocument], response.json())
1614
+ return pydantic.TypeAdapter(List[BlockDocument]).validate_python(
1615
+ response.json()
1616
+ )
1551
1617
 
1552
1618
  async def read_block_documents_by_type(
1553
1619
  self,
@@ -1576,28 +1642,27 @@ class PrefectClient:
1576
1642
  ),
1577
1643
  )
1578
1644
 
1579
- return pydantic.parse_obj_as(List[BlockDocument], response.json())
1645
+ return pydantic.TypeAdapter(List[BlockDocument]).validate_python(
1646
+ response.json()
1647
+ )
1580
1648
 
1581
1649
  async def create_deployment(
1582
1650
  self,
1583
1651
  flow_id: UUID,
1584
1652
  name: str,
1585
- version: str = None,
1586
- schedule: SCHEDULE_TYPES = None,
1587
- schedules: List[DeploymentScheduleCreate] = None,
1653
+ version: Optional[str] = None,
1654
+ schedules: Optional[List[DeploymentScheduleCreate]] = None,
1655
+ concurrency_limit: Optional[int] = None,
1588
1656
  parameters: Optional[Dict[str, Any]] = None,
1589
- description: str = None,
1590
- work_queue_name: str = None,
1591
- work_pool_name: str = None,
1592
- tags: List[str] = None,
1593
- storage_document_id: UUID = None,
1594
- manifest_path: str = None,
1595
- path: str = None,
1596
- entrypoint: str = None,
1597
- infrastructure_document_id: UUID = None,
1598
- infra_overrides: Optional[Dict[str, Any]] = None, # for backwards compat
1657
+ description: Optional[str] = None,
1658
+ work_queue_name: Optional[str] = None,
1659
+ work_pool_name: Optional[str] = None,
1660
+ tags: Optional[List[str]] = None,
1661
+ storage_document_id: Optional[UUID] = None,
1662
+ path: Optional[str] = None,
1663
+ entrypoint: Optional[str] = None,
1664
+ infrastructure_document_id: Optional[UUID] = None,
1599
1665
  parameter_openapi_schema: Optional[Dict[str, Any]] = None,
1600
- is_schedule_active: Optional[bool] = None,
1601
1666
  paused: Optional[bool] = None,
1602
1667
  pull_steps: Optional[List[dict]] = None,
1603
1668
  enforce_parameter_schema: Optional[bool] = None,
@@ -1610,7 +1675,6 @@ class PrefectClient:
1610
1675
  flow_id: the flow ID to create a deployment for
1611
1676
  name: the name of the deployment
1612
1677
  version: an optional version string for the deployment
1613
- schedule: an optional schedule to apply to the deployment
1614
1678
  tags: an optional list of tags to apply to the deployment
1615
1679
  storage_document_id: an reference to the storage block document
1616
1680
  used for the deployed flow
@@ -1627,8 +1691,9 @@ class PrefectClient:
1627
1691
  Returns:
1628
1692
  the ID of the deployment in the backend
1629
1693
  """
1630
- jv = handle_deprecated_infra_overrides_parameter(job_variables, infra_overrides)
1631
1694
 
1695
+ if parameter_openapi_schema is None:
1696
+ parameter_openapi_schema = {}
1632
1697
  deployment_create = DeploymentCreate(
1633
1698
  flow_id=flow_id,
1634
1699
  name=name,
@@ -1640,14 +1705,12 @@ class PrefectClient:
1640
1705
  storage_document_id=storage_document_id,
1641
1706
  path=path,
1642
1707
  entrypoint=entrypoint,
1643
- manifest_path=manifest_path, # for backwards compat
1644
1708
  infrastructure_document_id=infrastructure_document_id,
1645
- job_variables=jv,
1709
+ job_variables=dict(job_variables or {}),
1646
1710
  parameter_openapi_schema=parameter_openapi_schema,
1647
- is_schedule_active=is_schedule_active,
1648
1711
  paused=paused,
1649
- schedule=schedule,
1650
1712
  schedules=schedules or [],
1713
+ concurrency_limit=concurrency_limit,
1651
1714
  pull_steps=pull_steps,
1652
1715
  enforce_parameter_schema=enforce_parameter_schema,
1653
1716
  )
@@ -1659,12 +1722,9 @@ class PrefectClient:
1659
1722
  exclude = {
1660
1723
  field
1661
1724
  for field in ["work_pool_name", "work_queue_name"]
1662
- if field not in deployment_create.__fields_set__
1725
+ if field not in deployment_create.model_fields_set
1663
1726
  }
1664
1727
 
1665
- if deployment_create.is_schedule_active is None:
1666
- exclude.add("is_schedule_active")
1667
-
1668
1728
  if deployment_create.paused is None:
1669
1729
  exclude.add("paused")
1670
1730
 
@@ -1674,7 +1734,7 @@ class PrefectClient:
1674
1734
  if deployment_create.enforce_parameter_schema is None:
1675
1735
  exclude.add("enforce_parameter_schema")
1676
1736
 
1677
- json = deployment_create.dict(json_compatible=True, exclude=exclude)
1737
+ json = deployment_create.model_dump(mode="json", exclude=exclude)
1678
1738
  response = await self._client.post(
1679
1739
  "/deployments/",
1680
1740
  json=json,
@@ -1685,12 +1745,6 @@ class PrefectClient:
1685
1745
 
1686
1746
  return UUID(deployment_id)
1687
1747
 
1688
- async def update_schedule(self, deployment_id: UUID, active: bool = True):
1689
- path = "set_schedule_active" if active else "set_schedule_inactive"
1690
- await self._client.post(
1691
- f"/deployments/{deployment_id}/{path}",
1692
- )
1693
-
1694
1748
  async def set_deployment_paused_state(self, deployment_id: UUID, paused: bool):
1695
1749
  await self._client.patch(
1696
1750
  f"/deployments/{deployment_id}", json={"paused": paused}
@@ -1698,41 +1752,12 @@ class PrefectClient:
1698
1752
 
1699
1753
  async def update_deployment(
1700
1754
  self,
1701
- deployment: Deployment,
1702
- schedule: SCHEDULE_TYPES = None,
1703
- is_schedule_active: bool = None,
1755
+ deployment_id: UUID,
1756
+ deployment: DeploymentUpdate,
1704
1757
  ):
1705
- deployment_update = DeploymentUpdate(
1706
- version=deployment.version,
1707
- schedule=schedule if schedule is not None else deployment.schedule,
1708
- is_schedule_active=(
1709
- is_schedule_active
1710
- if is_schedule_active is not None
1711
- else deployment.is_schedule_active
1712
- ),
1713
- description=deployment.description,
1714
- work_queue_name=deployment.work_queue_name,
1715
- tags=deployment.tags,
1716
- manifest_path=deployment.manifest_path,
1717
- path=deployment.path,
1718
- entrypoint=deployment.entrypoint,
1719
- parameters=deployment.parameters,
1720
- storage_document_id=deployment.storage_document_id,
1721
- infrastructure_document_id=deployment.infrastructure_document_id,
1722
- job_variables=deployment.job_variables,
1723
- enforce_parameter_schema=deployment.enforce_parameter_schema,
1724
- )
1725
-
1726
- if getattr(deployment, "work_pool_name", None) is not None:
1727
- deployment_update.work_pool_name = deployment.work_pool_name
1728
-
1729
- exclude = set()
1730
- if deployment.enforce_parameter_schema is None:
1731
- exclude.add("enforce_parameter_schema")
1732
-
1733
1758
  await self._client.patch(
1734
- f"/deployments/{deployment.id}",
1735
- json=deployment_update.dict(json_compatible=True, exclude=exclude),
1759
+ f"/deployments/{deployment_id}",
1760
+ json=deployment.model_dump(mode="json", exclude_unset=True),
1736
1761
  )
1737
1762
 
1738
1763
  async def _create_deployment_from_schema(self, schema: DeploymentCreate) -> UUID:
@@ -1742,7 +1767,7 @@ class PrefectClient:
1742
1767
  # TODO: We are likely to remove this method once we have considered the
1743
1768
  # packaging interface for deployments further.
1744
1769
  response = await self._client.post(
1745
- "/deployments/", json=schema.dict(json_compatible=True)
1770
+ "/deployments/", json=schema.model_dump(mode="json")
1746
1771
  )
1747
1772
  deployment_id = response.json().get("id")
1748
1773
  if not deployment_id:
@@ -1763,6 +1788,12 @@ class PrefectClient:
1763
1788
  Returns:
1764
1789
  a [Deployment model][prefect.client.schemas.objects.Deployment] representation of the deployment
1765
1790
  """
1791
+ if not isinstance(deployment_id, UUID):
1792
+ try:
1793
+ deployment_id = UUID(deployment_id)
1794
+ except ValueError:
1795
+ raise ValueError(f"Invalid deployment ID: {deployment_id}")
1796
+
1766
1797
  try:
1767
1798
  response = await self._client.get(f"/deployments/{deployment_id}")
1768
1799
  except httpx.HTTPStatusError as e:
@@ -1770,7 +1801,7 @@ class PrefectClient:
1770
1801
  raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
1771
1802
  else:
1772
1803
  raise
1773
- return DeploymentResponse.parse_obj(response.json())
1804
+ return DeploymentResponse.model_validate(response.json())
1774
1805
 
1775
1806
  async def read_deployment_by_name(
1776
1807
  self,
@@ -1797,19 +1828,19 @@ class PrefectClient:
1797
1828
  else:
1798
1829
  raise
1799
1830
 
1800
- return DeploymentResponse.parse_obj(response.json())
1831
+ return DeploymentResponse.model_validate(response.json())
1801
1832
 
1802
1833
  async def read_deployments(
1803
1834
  self,
1804
1835
  *,
1805
- flow_filter: FlowFilter = None,
1806
- flow_run_filter: FlowRunFilter = None,
1807
- task_run_filter: TaskRunFilter = None,
1808
- deployment_filter: DeploymentFilter = None,
1809
- work_pool_filter: WorkPoolFilter = None,
1810
- work_queue_filter: WorkQueueFilter = None,
1811
- limit: int = None,
1812
- sort: DeploymentSort = None,
1836
+ flow_filter: Optional[FlowFilter] = None,
1837
+ flow_run_filter: Optional[FlowRunFilter] = None,
1838
+ task_run_filter: Optional[TaskRunFilter] = None,
1839
+ deployment_filter: Optional[DeploymentFilter] = None,
1840
+ work_pool_filter: Optional[WorkPoolFilter] = None,
1841
+ work_queue_filter: Optional[WorkQueueFilter] = None,
1842
+ limit: Optional[int] = None,
1843
+ sort: Optional[DeploymentSort] = None,
1813
1844
  offset: int = 0,
1814
1845
  ) -> List[DeploymentResponse]:
1815
1846
  """
@@ -1831,29 +1862,23 @@ class PrefectClient:
1831
1862
  of the deployments
1832
1863
  """
1833
1864
  body = {
1834
- "flows": flow_filter.dict(json_compatible=True) if flow_filter else None,
1865
+ "flows": flow_filter.model_dump(mode="json") if flow_filter else None,
1835
1866
  "flow_runs": (
1836
- flow_run_filter.dict(json_compatible=True, exclude_unset=True)
1867
+ flow_run_filter.model_dump(mode="json", exclude_unset=True)
1837
1868
  if flow_run_filter
1838
1869
  else None
1839
1870
  ),
1840
1871
  "task_runs": (
1841
- task_run_filter.dict(json_compatible=True) if task_run_filter else None
1872
+ task_run_filter.model_dump(mode="json") if task_run_filter else None
1842
1873
  ),
1843
1874
  "deployments": (
1844
- deployment_filter.dict(json_compatible=True)
1845
- if deployment_filter
1846
- else None
1875
+ deployment_filter.model_dump(mode="json") if deployment_filter else None
1847
1876
  ),
1848
1877
  "work_pools": (
1849
- work_pool_filter.dict(json_compatible=True)
1850
- if work_pool_filter
1851
- else None
1878
+ work_pool_filter.model_dump(mode="json") if work_pool_filter else None
1852
1879
  ),
1853
1880
  "work_pool_queues": (
1854
- work_queue_filter.dict(json_compatible=True)
1855
- if work_queue_filter
1856
- else None
1881
+ work_queue_filter.model_dump(mode="json") if work_queue_filter else None
1857
1882
  ),
1858
1883
  "limit": limit,
1859
1884
  "offset": offset,
@@ -1861,7 +1886,9 @@ class PrefectClient:
1861
1886
  }
1862
1887
 
1863
1888
  response = await self._client.post("/deployments/filter", json=body)
1864
- return pydantic.parse_obj_as(List[DeploymentResponse], response.json())
1889
+ return pydantic.TypeAdapter(List[DeploymentResponse]).validate_python(
1890
+ response.json()
1891
+ )
1865
1892
 
1866
1893
  async def delete_deployment(
1867
1894
  self,
@@ -1909,13 +1936,15 @@ class PrefectClient:
1909
1936
  ]
1910
1937
 
1911
1938
  json = [
1912
- deployment_schedule_create.dict(json_compatible=True)
1939
+ deployment_schedule_create.model_dump(mode="json")
1913
1940
  for deployment_schedule_create in deployment_schedule_create
1914
1941
  ]
1915
1942
  response = await self._client.post(
1916
1943
  f"/deployments/{deployment_id}/schedules", json=json
1917
1944
  )
1918
- return pydantic.parse_obj_as(List[DeploymentSchedule], response.json())
1945
+ return pydantic.TypeAdapter(List[DeploymentSchedule]).validate_python(
1946
+ response.json()
1947
+ )
1919
1948
 
1920
1949
  async def read_deployment_schedules(
1921
1950
  self,
@@ -1937,7 +1966,9 @@ class PrefectClient:
1937
1966
  raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
1938
1967
  else:
1939
1968
  raise
1940
- return pydantic.parse_obj_as(List[DeploymentSchedule], response.json())
1969
+ return pydantic.TypeAdapter(List[DeploymentSchedule]).validate_python(
1970
+ response.json()
1971
+ )
1941
1972
 
1942
1973
  async def update_deployment_schedule(
1943
1974
  self,
@@ -1962,7 +1993,7 @@ class PrefectClient:
1962
1993
  kwargs["schedule"] = schedule
1963
1994
 
1964
1995
  deployment_schedule_update = DeploymentScheduleUpdate(**kwargs)
1965
- json = deployment_schedule_update.dict(json_compatible=True, exclude_unset=True)
1996
+ json = deployment_schedule_update.model_dump(mode="json", exclude_unset=True)
1966
1997
 
1967
1998
  try:
1968
1999
  await self._client.patch(
@@ -2016,7 +2047,7 @@ class PrefectClient:
2016
2047
  raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
2017
2048
  else:
2018
2049
  raise
2019
- return FlowRun.parse_obj(response.json())
2050
+ return FlowRun.model_validate(response.json())
2020
2051
 
2021
2052
  async def resume_flow_run(
2022
2053
  self, flow_run_id: UUID, run_input: Optional[Dict] = None
@@ -2038,7 +2069,7 @@ class PrefectClient:
2038
2069
  except httpx.HTTPStatusError:
2039
2070
  raise
2040
2071
 
2041
- return OrchestrationResult.parse_obj(response.json())
2072
+ return OrchestrationResult.model_validate(response.json())
2042
2073
 
2043
2074
  async def read_flow_runs(
2044
2075
  self,
@@ -2050,7 +2081,7 @@ class PrefectClient:
2050
2081
  work_pool_filter: WorkPoolFilter = None,
2051
2082
  work_queue_filter: WorkQueueFilter = None,
2052
2083
  sort: FlowRunSort = None,
2053
- limit: int = None,
2084
+ limit: Optional[int] = None,
2054
2085
  offset: int = 0,
2055
2086
  ) -> List[FlowRun]:
2056
2087
  """
@@ -2073,29 +2104,23 @@ class PrefectClient:
2073
2104
  of the flow runs
2074
2105
  """
2075
2106
  body = {
2076
- "flows": flow_filter.dict(json_compatible=True) if flow_filter else None,
2107
+ "flows": flow_filter.model_dump(mode="json") if flow_filter else None,
2077
2108
  "flow_runs": (
2078
- flow_run_filter.dict(json_compatible=True, exclude_unset=True)
2109
+ flow_run_filter.model_dump(mode="json", exclude_unset=True)
2079
2110
  if flow_run_filter
2080
2111
  else None
2081
2112
  ),
2082
2113
  "task_runs": (
2083
- task_run_filter.dict(json_compatible=True) if task_run_filter else None
2114
+ task_run_filter.model_dump(mode="json") if task_run_filter else None
2084
2115
  ),
2085
2116
  "deployments": (
2086
- deployment_filter.dict(json_compatible=True)
2087
- if deployment_filter
2088
- else None
2117
+ deployment_filter.model_dump(mode="json") if deployment_filter else None
2089
2118
  ),
2090
2119
  "work_pools": (
2091
- work_pool_filter.dict(json_compatible=True)
2092
- if work_pool_filter
2093
- else None
2120
+ work_pool_filter.model_dump(mode="json") if work_pool_filter else None
2094
2121
  ),
2095
2122
  "work_pool_queues": (
2096
- work_queue_filter.dict(json_compatible=True)
2097
- if work_queue_filter
2098
- else None
2123
+ work_queue_filter.model_dump(mode="json") if work_queue_filter else None
2099
2124
  ),
2100
2125
  "sort": sort,
2101
2126
  "limit": limit,
@@ -2103,7 +2128,7 @@ class PrefectClient:
2103
2128
  }
2104
2129
 
2105
2130
  response = await self._client.post("/flow_runs/filter", json=body)
2106
- return pydantic.parse_obj_as(List[FlowRun], response.json())
2131
+ return pydantic.TypeAdapter(List[FlowRun]).validate_python(response.json())
2107
2132
 
2108
2133
  async def set_flow_run_state(
2109
2134
  self,
@@ -2123,13 +2148,16 @@ class PrefectClient:
2123
2148
  Returns:
2124
2149
  an OrchestrationResult model representation of state orchestration output
2125
2150
  """
2151
+ flow_run_id = (
2152
+ flow_run_id if isinstance(flow_run_id, UUID) else UUID(flow_run_id)
2153
+ )
2126
2154
  state_create = state.to_state_create()
2127
2155
  state_create.state_details.flow_run_id = flow_run_id
2128
2156
  state_create.state_details.transition_id = uuid4()
2129
2157
  try:
2130
2158
  response = await self._client.post(
2131
2159
  f"/flow_runs/{flow_run_id}/set_state",
2132
- json=dict(state=state_create.dict(json_compatible=True), force=force),
2160
+ json=dict(state=state_create.model_dump(mode="json"), force=force),
2133
2161
  )
2134
2162
  except httpx.HTTPStatusError as e:
2135
2163
  if e.response.status_code == status.HTTP_404_NOT_FOUND:
@@ -2137,7 +2165,7 @@ class PrefectClient:
2137
2165
  else:
2138
2166
  raise
2139
2167
 
2140
- return OrchestrationResult.parse_obj(response.json())
2168
+ return OrchestrationResult.model_validate(response.json())
2141
2169
 
2142
2170
  async def read_flow_run_states(
2143
2171
  self, flow_run_id: UUID
@@ -2155,13 +2183,15 @@ class PrefectClient:
2155
2183
  response = await self._client.get(
2156
2184
  "/flow_run_states/", params=dict(flow_run_id=str(flow_run_id))
2157
2185
  )
2158
- return pydantic.parse_obj_as(List[prefect.states.State], response.json())
2186
+ return pydantic.TypeAdapter(List[prefect.states.State]).validate_python(
2187
+ response.json()
2188
+ )
2159
2189
 
2160
2190
  async def set_task_run_name(self, task_run_id: UUID, name: str):
2161
2191
  task_run_data = TaskRunUpdate(name=name)
2162
2192
  return await self._client.patch(
2163
2193
  f"/task_runs/{task_run_id}",
2164
- json=task_run_data.dict(json_compatible=True, exclude_unset=True),
2194
+ json=task_run_data.model_dump(mode="json", exclude_unset=True),
2165
2195
  )
2166
2196
 
2167
2197
  async def create_task_run(
@@ -2169,6 +2199,7 @@ class PrefectClient:
2169
2199
  task: "TaskObject[P, R]",
2170
2200
  flow_run_id: Optional[UUID],
2171
2201
  dynamic_key: str,
2202
+ id: Optional[UUID] = None,
2172
2203
  name: Optional[str] = None,
2173
2204
  extra_tags: Optional[Iterable[str]] = None,
2174
2205
  state: Optional[prefect.states.State[R]] = None,
@@ -2192,6 +2223,8 @@ class PrefectClient:
2192
2223
  task: The Task to run
2193
2224
  flow_run_id: The flow run id with which to associate the task run
2194
2225
  dynamic_key: A key unique to this particular run of a Task within the flow
2226
+ id: An optional ID for the task run. If not provided, one will be generated
2227
+ server-side.
2195
2228
  name: An optional name for the task run
2196
2229
  extra_tags: an optional list of extra tags to apply to the task run in
2197
2230
  addition to `task.tags`
@@ -2208,10 +2241,11 @@ class PrefectClient:
2208
2241
  state = prefect.states.Pending()
2209
2242
 
2210
2243
  task_run_data = TaskRunCreate(
2244
+ id=id,
2211
2245
  name=name,
2212
2246
  flow_run_id=flow_run_id,
2213
2247
  task_key=task.task_key,
2214
- dynamic_key=dynamic_key,
2248
+ dynamic_key=str(dynamic_key),
2215
2249
  tags=list(tags),
2216
2250
  task_version=task.version,
2217
2251
  empirical_policy=TaskRunPolicy(
@@ -2222,11 +2256,10 @@ class PrefectClient:
2222
2256
  state=state.to_state_create(),
2223
2257
  task_inputs=task_inputs or {},
2224
2258
  )
2259
+ content = task_run_data.model_dump_json(exclude={"id"} if id is None else None)
2225
2260
 
2226
- response = await self._client.post(
2227
- "/task_runs/", json=task_run_data.dict(json_compatible=True)
2228
- )
2229
- return TaskRun.parse_obj(response.json())
2261
+ response = await self._client.post("/task_runs/", content=content)
2262
+ return TaskRun.model_validate(response.json())
2230
2263
 
2231
2264
  async def read_task_run(self, task_run_id: UUID) -> TaskRun:
2232
2265
  """
@@ -2238,8 +2271,14 @@ class PrefectClient:
2238
2271
  Returns:
2239
2272
  a Task Run model representation of the task run
2240
2273
  """
2241
- response = await self._client.get(f"/task_runs/{task_run_id}")
2242
- return TaskRun.parse_obj(response.json())
2274
+ try:
2275
+ response = await self._client.get(f"/task_runs/{task_run_id}")
2276
+ return TaskRun.model_validate(response.json())
2277
+ except httpx.HTTPStatusError as e:
2278
+ if e.response.status_code == status.HTTP_404_NOT_FOUND:
2279
+ raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
2280
+ else:
2281
+ raise
2243
2282
 
2244
2283
  async def read_task_runs(
2245
2284
  self,
@@ -2249,7 +2288,7 @@ class PrefectClient:
2249
2288
  task_run_filter: TaskRunFilter = None,
2250
2289
  deployment_filter: DeploymentFilter = None,
2251
2290
  sort: TaskRunSort = None,
2252
- limit: int = None,
2291
+ limit: Optional[int] = None,
2253
2292
  offset: int = 0,
2254
2293
  ) -> List[TaskRun]:
2255
2294
  """
@@ -2270,26 +2309,24 @@ class PrefectClient:
2270
2309
  of the task runs
2271
2310
  """
2272
2311
  body = {
2273
- "flows": flow_filter.dict(json_compatible=True) if flow_filter else None,
2312
+ "flows": flow_filter.model_dump(mode="json") if flow_filter else None,
2274
2313
  "flow_runs": (
2275
- flow_run_filter.dict(json_compatible=True, exclude_unset=True)
2314
+ flow_run_filter.model_dump(mode="json", exclude_unset=True)
2276
2315
  if flow_run_filter
2277
2316
  else None
2278
2317
  ),
2279
2318
  "task_runs": (
2280
- task_run_filter.dict(json_compatible=True) if task_run_filter else None
2319
+ task_run_filter.model_dump(mode="json") if task_run_filter else None
2281
2320
  ),
2282
2321
  "deployments": (
2283
- deployment_filter.dict(json_compatible=True)
2284
- if deployment_filter
2285
- else None
2322
+ deployment_filter.model_dump(mode="json") if deployment_filter else None
2286
2323
  ),
2287
2324
  "sort": sort,
2288
2325
  "limit": limit,
2289
2326
  "offset": offset,
2290
2327
  }
2291
2328
  response = await self._client.post("/task_runs/filter", json=body)
2292
- return pydantic.parse_obj_as(List[TaskRun], response.json())
2329
+ return pydantic.TypeAdapter(List[TaskRun]).validate_python(response.json())
2293
2330
 
2294
2331
  async def delete_task_run(self, task_run_id: UUID) -> None:
2295
2332
  """
@@ -2331,9 +2368,9 @@ class PrefectClient:
2331
2368
  state_create.state_details.task_run_id = task_run_id
2332
2369
  response = await self._client.post(
2333
2370
  f"/task_runs/{task_run_id}/set_state",
2334
- json=dict(state=state_create.dict(json_compatible=True), force=force),
2371
+ json=dict(state=state_create.model_dump(mode="json"), force=force),
2335
2372
  )
2336
- return OrchestrationResult.parse_obj(response.json())
2373
+ return OrchestrationResult.model_validate(response.json())
2337
2374
 
2338
2375
  async def read_task_run_states(
2339
2376
  self, task_run_id: UUID
@@ -2350,7 +2387,9 @@ class PrefectClient:
2350
2387
  response = await self._client.get(
2351
2388
  "/task_run_states/", params=dict(task_run_id=str(task_run_id))
2352
2389
  )
2353
- return pydantic.parse_obj_as(List[prefect.states.State], response.json())
2390
+ return pydantic.TypeAdapter(List[prefect.states.State]).validate_python(
2391
+ response.json()
2392
+ )
2354
2393
 
2355
2394
  async def create_logs(self, logs: Iterable[Union[LogCreate, dict]]) -> None:
2356
2395
  """
@@ -2360,7 +2399,7 @@ class PrefectClient:
2360
2399
  logs: An iterable of `LogCreate` objects or already json-compatible dicts
2361
2400
  """
2362
2401
  serialized_logs = [
2363
- log.dict(json_compatible=True) if isinstance(log, LogCreate) else log
2402
+ log.model_dump(mode="json") if isinstance(log, LogCreate) else log
2364
2403
  for log in logs
2365
2404
  ]
2366
2405
  await self._client.post("/logs/", json=serialized_logs)
@@ -2397,7 +2436,7 @@ class PrefectClient:
2397
2436
  )
2398
2437
  response = await self._client.post(
2399
2438
  "/flow_run_notification_policies/",
2400
- json=policy.dict(json_compatible=True),
2439
+ json=policy.model_dump(mode="json"),
2401
2440
  )
2402
2441
 
2403
2442
  policy_id = response.json().get("id")
@@ -2467,7 +2506,7 @@ class PrefectClient:
2467
2506
  try:
2468
2507
  await self._client.patch(
2469
2508
  f"/flow_run_notification_policies/{id}",
2470
- json=policy.dict(json_compatible=True, exclude_unset=True),
2509
+ json=policy.model_dump(mode="json", exclude_unset=True),
2471
2510
  )
2472
2511
  except httpx.HTTPStatusError as e:
2473
2512
  if e.response.status_code == status.HTTP_404_NOT_FOUND:
@@ -2496,7 +2535,7 @@ class PrefectClient:
2496
2535
  """
2497
2536
  body = {
2498
2537
  "flow_run_notification_policy_filter": (
2499
- flow_run_notification_policy_filter.dict(json_compatible=True)
2538
+ flow_run_notification_policy_filter.model_dump(mode="json")
2500
2539
  if flow_run_notification_policy_filter
2501
2540
  else None
2502
2541
  ),
@@ -2506,58 +2545,29 @@ class PrefectClient:
2506
2545
  response = await self._client.post(
2507
2546
  "/flow_run_notification_policies/filter", json=body
2508
2547
  )
2509
- return pydantic.parse_obj_as(List[FlowRunNotificationPolicy], response.json())
2548
+ return pydantic.TypeAdapter(List[FlowRunNotificationPolicy]).validate_python(
2549
+ response.json()
2550
+ )
2510
2551
 
2511
2552
  async def read_logs(
2512
2553
  self,
2513
2554
  log_filter: LogFilter = None,
2514
- limit: int = None,
2515
- offset: int = None,
2555
+ limit: Optional[int] = None,
2556
+ offset: Optional[int] = None,
2516
2557
  sort: LogSort = LogSort.TIMESTAMP_ASC,
2517
2558
  ) -> List[Log]:
2518
2559
  """
2519
2560
  Read flow and task run logs.
2520
2561
  """
2521
2562
  body = {
2522
- "logs": log_filter.dict(json_compatible=True) if log_filter else None,
2563
+ "logs": log_filter.model_dump(mode="json") if log_filter else None,
2523
2564
  "limit": limit,
2524
2565
  "offset": offset,
2525
2566
  "sort": sort,
2526
2567
  }
2527
2568
 
2528
2569
  response = await self._client.post("/logs/filter", json=body)
2529
- return pydantic.parse_obj_as(List[Log], response.json())
2530
-
2531
- async def resolve_datadoc(self, datadoc: DataDocument) -> Any:
2532
- """
2533
- Recursively decode possibly nested data documents.
2534
-
2535
- "server" encoded documents will be retrieved from the server.
2536
-
2537
- Args:
2538
- datadoc: The data document to resolve
2539
-
2540
- Returns:
2541
- a decoded object, the innermost data
2542
- """
2543
- if not isinstance(datadoc, DataDocument):
2544
- raise TypeError(
2545
- f"`resolve_datadoc` received invalid type {type(datadoc).__name__}"
2546
- )
2547
-
2548
- async def resolve_inner(data):
2549
- if isinstance(data, bytes):
2550
- try:
2551
- data = DataDocument.parse_raw(data)
2552
- except pydantic.ValidationError:
2553
- return data
2554
-
2555
- if isinstance(data, DataDocument):
2556
- return await resolve_inner(data.decode())
2557
-
2558
- return data
2559
-
2560
- return await resolve_inner(datadoc)
2570
+ return pydantic.TypeAdapter(List[Log]).validate_python(response.json())
2561
2571
 
2562
2572
  async def send_worker_heartbeat(
2563
2573
  self,
@@ -2601,7 +2611,7 @@ class PrefectClient:
2601
2611
  f"/work_pools/{work_pool_name}/workers/filter",
2602
2612
  json={
2603
2613
  "worker_filter": (
2604
- worker_filter.dict(json_compatible=True, exclude_unset=True)
2614
+ worker_filter.model_dump(mode="json", exclude_unset=True)
2605
2615
  if worker_filter
2606
2616
  else None
2607
2617
  ),
@@ -2610,7 +2620,7 @@ class PrefectClient:
2610
2620
  },
2611
2621
  )
2612
2622
 
2613
- return pydantic.parse_obj_as(List[Worker], response.json())
2623
+ return pydantic.TypeAdapter(List[Worker]).validate_python(response.json())
2614
2624
 
2615
2625
  async def read_work_pool(self, work_pool_name: str) -> WorkPool:
2616
2626
  """
@@ -2625,7 +2635,7 @@ class PrefectClient:
2625
2635
  """
2626
2636
  try:
2627
2637
  response = await self._client.get(f"/work_pools/{work_pool_name}")
2628
- return pydantic.parse_obj_as(WorkPool, response.json())
2638
+ return WorkPool.model_validate(response.json())
2629
2639
  except httpx.HTTPStatusError as e:
2630
2640
  if e.response.status_code == status.HTTP_404_NOT_FOUND:
2631
2641
  raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
@@ -2654,17 +2664,16 @@ class PrefectClient:
2654
2664
  "limit": limit,
2655
2665
  "offset": offset,
2656
2666
  "work_pools": (
2657
- work_pool_filter.dict(json_compatible=True)
2658
- if work_pool_filter
2659
- else None
2667
+ work_pool_filter.model_dump(mode="json") if work_pool_filter else None
2660
2668
  ),
2661
2669
  }
2662
2670
  response = await self._client.post("/work_pools/filter", json=body)
2663
- return pydantic.parse_obj_as(List[WorkPool], response.json())
2671
+ return pydantic.TypeAdapter(List[WorkPool]).validate_python(response.json())
2664
2672
 
2665
2673
  async def create_work_pool(
2666
2674
  self,
2667
2675
  work_pool: WorkPoolCreate,
2676
+ overwrite: bool = False,
2668
2677
  ) -> WorkPool:
2669
2678
  """
2670
2679
  Creates a work pool with the provided configuration.
@@ -2678,15 +2687,32 @@ class PrefectClient:
2678
2687
  try:
2679
2688
  response = await self._client.post(
2680
2689
  "/work_pools/",
2681
- json=work_pool.dict(json_compatible=True, exclude_unset=True),
2690
+ json=work_pool.model_dump(mode="json", exclude_unset=True),
2682
2691
  )
2683
2692
  except httpx.HTTPStatusError as e:
2684
2693
  if e.response.status_code == status.HTTP_409_CONFLICT:
2685
- raise prefect.exceptions.ObjectAlreadyExists(http_exc=e) from e
2694
+ if overwrite:
2695
+ existing_work_pool = await self.read_work_pool(
2696
+ work_pool_name=work_pool.name
2697
+ )
2698
+ if existing_work_pool.type != work_pool.type:
2699
+ warnings.warn(
2700
+ "Overwriting work pool type is not supported. Ignoring provided type.",
2701
+ category=UserWarning,
2702
+ )
2703
+ await self.update_work_pool(
2704
+ work_pool_name=work_pool.name,
2705
+ work_pool=WorkPoolUpdate.model_validate(
2706
+ work_pool.model_dump(exclude={"name", "type"})
2707
+ ),
2708
+ )
2709
+ response = await self._client.get(f"/work_pools/{work_pool.name}")
2710
+ else:
2711
+ raise prefect.exceptions.ObjectAlreadyExists(http_exc=e) from e
2686
2712
  else:
2687
2713
  raise
2688
2714
 
2689
- return pydantic.parse_obj_as(WorkPool, response.json())
2715
+ return WorkPool.model_validate(response.json())
2690
2716
 
2691
2717
  async def update_work_pool(
2692
2718
  self,
@@ -2703,7 +2729,7 @@ class PrefectClient:
2703
2729
  try:
2704
2730
  await self._client.patch(
2705
2731
  f"/work_pools/{work_pool_name}",
2706
- json=work_pool.dict(json_compatible=True, exclude_unset=True),
2732
+ json=work_pool.model_dump(mode="json", exclude_unset=True),
2707
2733
  )
2708
2734
  except httpx.HTTPStatusError as e:
2709
2735
  if e.response.status_code == status.HTTP_404_NOT_FOUND:
@@ -2750,7 +2776,7 @@ class PrefectClient:
2750
2776
  """
2751
2777
  json = {
2752
2778
  "work_queues": (
2753
- work_queue_filter.dict(json_compatible=True, exclude_unset=True)
2779
+ work_queue_filter.model_dump(mode="json", exclude_unset=True)
2754
2780
  if work_queue_filter
2755
2781
  else None
2756
2782
  ),
@@ -2772,14 +2798,14 @@ class PrefectClient:
2772
2798
  else:
2773
2799
  response = await self._client.post("/work_queues/filter", json=json)
2774
2800
 
2775
- return pydantic.parse_obj_as(List[WorkQueue], response.json())
2801
+ return pydantic.TypeAdapter(List[WorkQueue]).validate_python(response.json())
2776
2802
 
2777
2803
  async def get_scheduled_flow_runs_for_deployments(
2778
2804
  self,
2779
2805
  deployment_ids: List[UUID],
2780
2806
  scheduled_before: Optional[datetime.datetime] = None,
2781
2807
  limit: Optional[int] = None,
2782
- ):
2808
+ ) -> List[FlowRunResponse]:
2783
2809
  body: Dict[str, Any] = dict(deployment_ids=[str(id) for id in deployment_ids])
2784
2810
  if scheduled_before:
2785
2811
  body["scheduled_before"] = str(scheduled_before)
@@ -2791,7 +2817,9 @@ class PrefectClient:
2791
2817
  json=body,
2792
2818
  )
2793
2819
 
2794
- return pydantic.parse_obj_as(List[FlowRunResponse], response.json())
2820
+ return pydantic.TypeAdapter(List[FlowRunResponse]).validate_python(
2821
+ response.json()
2822
+ )
2795
2823
 
2796
2824
  async def get_scheduled_flow_runs_for_work_pool(
2797
2825
  self,
@@ -2824,7 +2852,9 @@ class PrefectClient:
2824
2852
  f"/work_pools/{work_pool_name}/get_scheduled_flow_runs",
2825
2853
  json=body,
2826
2854
  )
2827
- return pydantic.parse_obj_as(List[WorkerFlowRunResponse], response.json())
2855
+ return pydantic.TypeAdapter(List[WorkerFlowRunResponse]).validate_python(
2856
+ response.json()
2857
+ )
2828
2858
 
2829
2859
  async def create_artifact(
2830
2860
  self,
@@ -2841,10 +2871,29 @@ class PrefectClient:
2841
2871
 
2842
2872
  response = await self._client.post(
2843
2873
  "/artifacts/",
2844
- json=artifact.dict(json_compatible=True, exclude_unset=True),
2874
+ json=artifact.model_dump(mode="json", exclude_unset=True),
2845
2875
  )
2846
2876
 
2847
- return pydantic.parse_obj_as(Artifact, response.json())
2877
+ return Artifact.model_validate(response.json())
2878
+
2879
+ async def update_artifact(
2880
+ self,
2881
+ artifact_id: UUID,
2882
+ artifact: ArtifactUpdate,
2883
+ ) -> None:
2884
+ """
2885
+ Updates an artifact
2886
+
2887
+ Args:
2888
+ artifact: Desired values for the updated artifact.
2889
+ Returns:
2890
+ Information about the updated artifact.
2891
+ """
2892
+
2893
+ await self._client.patch(
2894
+ f"/artifacts/{artifact_id}",
2895
+ json=artifact.model_dump(mode="json", exclude_unset=True),
2896
+ )
2848
2897
 
2849
2898
  async def read_artifacts(
2850
2899
  self,
@@ -2853,7 +2902,7 @@ class PrefectClient:
2853
2902
  flow_run_filter: FlowRunFilter = None,
2854
2903
  task_run_filter: TaskRunFilter = None,
2855
2904
  sort: ArtifactSort = None,
2856
- limit: int = None,
2905
+ limit: Optional[int] = None,
2857
2906
  offset: int = 0,
2858
2907
  ) -> List[Artifact]:
2859
2908
  """
@@ -2871,20 +2920,20 @@ class PrefectClient:
2871
2920
  """
2872
2921
  body = {
2873
2922
  "artifacts": (
2874
- artifact_filter.dict(json_compatible=True) if artifact_filter else None
2923
+ artifact_filter.model_dump(mode="json") if artifact_filter else None
2875
2924
  ),
2876
2925
  "flow_runs": (
2877
- flow_run_filter.dict(json_compatible=True) if flow_run_filter else None
2926
+ flow_run_filter.model_dump(mode="json") if flow_run_filter else None
2878
2927
  ),
2879
2928
  "task_runs": (
2880
- task_run_filter.dict(json_compatible=True) if task_run_filter else None
2929
+ task_run_filter.model_dump(mode="json") if task_run_filter else None
2881
2930
  ),
2882
2931
  "sort": sort,
2883
2932
  "limit": limit,
2884
2933
  "offset": offset,
2885
2934
  }
2886
2935
  response = await self._client.post("/artifacts/filter", json=body)
2887
- return pydantic.parse_obj_as(List[Artifact], response.json())
2936
+ return pydantic.TypeAdapter(List[Artifact]).validate_python(response.json())
2888
2937
 
2889
2938
  async def read_latest_artifacts(
2890
2939
  self,
@@ -2893,7 +2942,7 @@ class PrefectClient:
2893
2942
  flow_run_filter: FlowRunFilter = None,
2894
2943
  task_run_filter: TaskRunFilter = None,
2895
2944
  sort: ArtifactCollectionSort = None,
2896
- limit: int = None,
2945
+ limit: Optional[int] = None,
2897
2946
  offset: int = 0,
2898
2947
  ) -> List[ArtifactCollection]:
2899
2948
  """
@@ -2911,20 +2960,22 @@ class PrefectClient:
2911
2960
  """
2912
2961
  body = {
2913
2962
  "artifacts": (
2914
- artifact_filter.dict(json_compatible=True) if artifact_filter else None
2963
+ artifact_filter.model_dump(mode="json") if artifact_filter else None
2915
2964
  ),
2916
2965
  "flow_runs": (
2917
- flow_run_filter.dict(json_compatible=True) if flow_run_filter else None
2966
+ flow_run_filter.model_dump(mode="json") if flow_run_filter else None
2918
2967
  ),
2919
2968
  "task_runs": (
2920
- task_run_filter.dict(json_compatible=True) if task_run_filter else None
2969
+ task_run_filter.model_dump(mode="json") if task_run_filter else None
2921
2970
  ),
2922
2971
  "sort": sort,
2923
2972
  "limit": limit,
2924
2973
  "offset": offset,
2925
2974
  }
2926
2975
  response = await self._client.post("/artifacts/latest/filter", json=body)
2927
- return pydantic.parse_obj_as(List[ArtifactCollection], response.json())
2976
+ return pydantic.TypeAdapter(List[ArtifactCollection]).validate_python(
2977
+ response.json()
2978
+ )
2928
2979
 
2929
2980
  async def delete_artifact(self, artifact_id: UUID) -> None:
2930
2981
  """
@@ -2952,7 +3003,7 @@ class PrefectClient:
2952
3003
  """
2953
3004
  response = await self._client.post(
2954
3005
  "/variables/",
2955
- json=variable.dict(json_compatible=True, exclude_unset=True),
3006
+ json=variable.model_dump(mode="json", exclude_unset=True),
2956
3007
  )
2957
3008
  return Variable(**response.json())
2958
3009
 
@@ -2967,7 +3018,7 @@ class PrefectClient:
2967
3018
  """
2968
3019
  await self._client.patch(
2969
3020
  f"/variables/name/{variable.name}",
2970
- json=variable.dict(json_compatible=True, exclude_unset=True),
3021
+ json=variable.model_dump(mode="json", exclude_unset=True),
2971
3022
  )
2972
3023
 
2973
3024
  async def read_variable_by_name(self, name: str) -> Optional[Variable]:
@@ -2991,10 +3042,10 @@ class PrefectClient:
2991
3042
  else:
2992
3043
  raise
2993
3044
 
2994
- async def read_variables(self, limit: int = None) -> List[Variable]:
3045
+ async def read_variables(self, limit: Optional[int] = None) -> List[Variable]:
2995
3046
  """Reads all variables."""
2996
3047
  response = await self._client.post("/variables/filter", json={"limit": limit})
2997
- return pydantic.parse_obj_as(List[Variable], response.json())
3048
+ return pydantic.TypeAdapter(List[Variable]).validate_python(response.json())
2998
3049
 
2999
3050
  async def read_worker_metadata(self) -> Dict[str, Any]:
3000
3051
  """Reads worker metadata stored in Prefect collection registry."""
@@ -3003,16 +3054,34 @@ class PrefectClient:
3003
3054
  return response.json()
3004
3055
 
3005
3056
  async def increment_concurrency_slots(
3006
- self, names: List[str], slots: int, mode: str
3057
+ self, names: List[str], slots: int, mode: str, create_if_missing: Optional[bool]
3007
3058
  ) -> httpx.Response:
3008
3059
  return await self._client.post(
3009
3060
  "/v2/concurrency_limits/increment",
3010
- json={"names": names, "slots": slots, "mode": mode},
3061
+ json={
3062
+ "names": names,
3063
+ "slots": slots,
3064
+ "mode": mode,
3065
+ "create_if_missing": create_if_missing,
3066
+ },
3011
3067
  )
3012
3068
 
3013
3069
  async def release_concurrency_slots(
3014
3070
  self, names: List[str], slots: int, occupancy_seconds: float
3015
3071
  ) -> httpx.Response:
3072
+ """
3073
+ Release concurrency slots for the specified limits.
3074
+
3075
+ Args:
3076
+ names (List[str]): A list of limit names for which to release slots.
3077
+ slots (int): The number of concurrency slots to release.
3078
+ occupancy_seconds (float): The duration in seconds that the slots
3079
+ were occupied.
3080
+
3081
+ Returns:
3082
+ httpx.Response: The HTTP response from the server.
3083
+ """
3084
+
3016
3085
  return await self._client.post(
3017
3086
  "/v2/concurrency_limits/decrement",
3018
3087
  json={
@@ -3027,7 +3096,7 @@ class PrefectClient:
3027
3096
  ) -> UUID:
3028
3097
  response = await self._client.post(
3029
3098
  "/v2/concurrency_limits/",
3030
- json=concurrency_limit.dict(json_compatible=True, exclude_unset=True),
3099
+ json=concurrency_limit.model_dump(mode="json", exclude_unset=True),
3031
3100
  )
3032
3101
  return UUID(response.json()["id"])
3033
3102
 
@@ -3037,7 +3106,7 @@ class PrefectClient:
3037
3106
  try:
3038
3107
  response = await self._client.patch(
3039
3108
  f"/v2/concurrency_limits/{name}",
3040
- json=concurrency_limit.dict(json_compatible=True, exclude_unset=True),
3109
+ json=concurrency_limit.model_dump(mode="json", exclude_unset=True),
3041
3110
  )
3042
3111
  return response
3043
3112
  except httpx.HTTPStatusError as e:
@@ -3063,7 +3132,7 @@ class PrefectClient:
3063
3132
  ) -> GlobalConcurrencyLimitResponse:
3064
3133
  try:
3065
3134
  response = await self._client.get(f"/v2/concurrency_limits/{name}")
3066
- return GlobalConcurrencyLimitResponse.parse_obj(response.json())
3135
+ return GlobalConcurrencyLimitResponse.model_validate(response.json())
3067
3136
  except httpx.HTTPStatusError as e:
3068
3137
  if e.response.status_code == status.HTTP_404_NOT_FOUND:
3069
3138
  raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
@@ -3080,9 +3149,9 @@ class PrefectClient:
3080
3149
  "offset": offset,
3081
3150
  },
3082
3151
  )
3083
- return pydantic.parse_obj_as(
3084
- List[GlobalConcurrencyLimitResponse], response.json()
3085
- )
3152
+ return pydantic.TypeAdapter(
3153
+ List[GlobalConcurrencyLimitResponse]
3154
+ ).validate_python(response.json())
3086
3155
 
3087
3156
  async def create_flow_run_input(
3088
3157
  self, flow_run_id: UUID, key: str, value: str, sender: Optional[str] = None
@@ -3118,7 +3187,7 @@ class PrefectClient:
3118
3187
  },
3119
3188
  )
3120
3189
  response.raise_for_status()
3121
- return pydantic.parse_obj_as(List[FlowRunInput], response.json())
3190
+ return pydantic.TypeAdapter(List[FlowRunInput]).validate_python(response.json())
3122
3191
 
3123
3192
  async def read_flow_run_input(self, flow_run_id: UUID, key: str) -> str:
3124
3193
  """
@@ -3143,52 +3212,30 @@ class PrefectClient:
3143
3212
  response = await self._client.delete(f"/flow_runs/{flow_run_id}/input/{key}")
3144
3213
  response.raise_for_status()
3145
3214
 
3146
- def _raise_for_unsupported_automations(self) -> NoReturn:
3147
- if not PREFECT_EXPERIMENTAL_EVENTS:
3148
- raise RuntimeError(
3149
- "The current server and client configuration does not support "
3150
- "events. Enable experimental events support with the "
3151
- "PREFECT_EXPERIMENTAL_EVENTS setting."
3152
- )
3153
- else:
3154
- raise RuntimeError(
3155
- "The current server and client configuration does not support "
3156
- "automations. Enable experimental automations with the "
3157
- "PREFECT_API_SERVICES_TRIGGERS_ENABLED setting."
3158
- )
3159
-
3160
3215
  async def create_automation(self, automation: AutomationCore) -> UUID:
3161
3216
  """Creates an automation in Prefect Cloud."""
3162
- if not self.server_type.supports_automations():
3163
- self._raise_for_unsupported_automations()
3164
-
3165
3217
  response = await self._client.post(
3166
3218
  "/automations/",
3167
- json=automation.dict(json_compatible=True),
3219
+ json=automation.model_dump(mode="json"),
3168
3220
  )
3169
3221
 
3170
3222
  return UUID(response.json()["id"])
3171
3223
 
3172
3224
  async def update_automation(self, automation_id: UUID, automation: AutomationCore):
3173
3225
  """Updates an automation in Prefect Cloud."""
3174
- if not self.server_type.supports_automations():
3175
- self._raise_for_unsupported_automations()
3176
3226
  response = await self._client.put(
3177
3227
  f"/automations/{automation_id}",
3178
- json=automation.dict(json_compatible=True, exclude_unset=True),
3228
+ json=automation.model_dump(mode="json", exclude_unset=True),
3179
3229
  )
3180
3230
  response.raise_for_status
3181
3231
 
3182
3232
  async def read_automations(self) -> List[Automation]:
3183
- if not self.server_type.supports_automations():
3184
- self._raise_for_unsupported_automations()
3185
-
3186
3233
  response = await self._client.post("/automations/filter")
3187
3234
  response.raise_for_status()
3188
- return pydantic.parse_obj_as(List[Automation], response.json())
3235
+ return pydantic.TypeAdapter(List[Automation]).validate_python(response.json())
3189
3236
 
3190
3237
  async def find_automation(
3191
- self, id_or_name: Union[str, UUID], exit_if_not_found: bool = True
3238
+ self, id_or_name: Union[str, UUID]
3192
3239
  ) -> Optional[Automation]:
3193
3240
  if isinstance(id_or_name, str):
3194
3241
  try:
@@ -3221,14 +3268,11 @@ class PrefectClient:
3221
3268
  return None
3222
3269
 
3223
3270
  async def read_automation(self, automation_id: UUID) -> Optional[Automation]:
3224
- if not self.server_type.supports_automations():
3225
- self._raise_for_unsupported_automations()
3226
-
3227
3271
  response = await self._client.get(f"/automations/{automation_id}")
3228
3272
  if response.status_code == 404:
3229
3273
  return None
3230
3274
  response.raise_for_status()
3231
- return Automation.parse_obj(response.json())
3275
+ return Automation.model_validate(response.json())
3232
3276
 
3233
3277
  async def read_automations_by_name(self, name: str) -> List[Automation]:
3234
3278
  """
@@ -3240,15 +3284,13 @@ class PrefectClient:
3240
3284
  Returns:
3241
3285
  a list of Automation model representations of the automations
3242
3286
  """
3243
- if not self.server_type.supports_automations():
3244
- self._raise_for_unsupported_automations()
3245
3287
  automation_filter = filters.AutomationFilter(name=dict(any_=[name]))
3246
3288
 
3247
3289
  response = await self._client.post(
3248
3290
  "/automations/filter",
3249
3291
  json={
3250
3292
  "sort": sorting.AutomationSort.UPDATED_DESC,
3251
- "automations": automation_filter.dict(json_compatible=True)
3293
+ "automations": automation_filter.model_dump(mode="json")
3252
3294
  if automation_filter
3253
3295
  else None,
3254
3296
  },
@@ -3256,30 +3298,21 @@ class PrefectClient:
3256
3298
 
3257
3299
  response.raise_for_status()
3258
3300
 
3259
- return pydantic.parse_obj_as(List[Automation], response.json())
3301
+ return pydantic.TypeAdapter(List[Automation]).validate_python(response.json())
3260
3302
 
3261
3303
  async def pause_automation(self, automation_id: UUID):
3262
- if not self.server_type.supports_automations():
3263
- self._raise_for_unsupported_automations()
3264
-
3265
3304
  response = await self._client.patch(
3266
3305
  f"/automations/{automation_id}", json={"enabled": False}
3267
3306
  )
3268
3307
  response.raise_for_status()
3269
3308
 
3270
3309
  async def resume_automation(self, automation_id: UUID):
3271
- if not self.server_type.supports_automations():
3272
- self._raise_for_unsupported_automations()
3273
-
3274
3310
  response = await self._client.patch(
3275
3311
  f"/automations/{automation_id}", json={"enabled": True}
3276
3312
  )
3277
3313
  response.raise_for_status()
3278
3314
 
3279
3315
  async def delete_automation(self, automation_id: UUID):
3280
- if not self.server_type.supports_automations():
3281
- self._raise_for_unsupported_automations()
3282
-
3283
3316
  response = await self._client.delete(f"/automations/{automation_id}")
3284
3317
  if response.status_code == 404:
3285
3318
  return
@@ -3289,17 +3322,11 @@ class PrefectClient:
3289
3322
  async def read_resource_related_automations(
3290
3323
  self, resource_id: str
3291
3324
  ) -> List[Automation]:
3292
- if not self.server_type.supports_automations():
3293
- self._raise_for_unsupported_automations()
3294
-
3295
3325
  response = await self._client.get(f"/automations/related-to/{resource_id}")
3296
3326
  response.raise_for_status()
3297
- return pydantic.parse_obj_as(List[Automation], response.json())
3327
+ return pydantic.TypeAdapter(List[Automation]).validate_python(response.json())
3298
3328
 
3299
3329
  async def delete_resource_owned_automations(self, resource_id: str):
3300
- if not self.server_type.supports_automations():
3301
- self._raise_for_unsupported_automations()
3302
-
3303
3330
  await self._client.delete(f"/automations/owned-by/{resource_id}")
3304
3331
 
3305
3332
  async def __aenter__(self):
@@ -3318,9 +3345,11 @@ class PrefectClient:
3318
3345
  "Retrieve a new client with `get_client()` instead."
3319
3346
  )
3320
3347
 
3348
+ self._context_stack += 1
3349
+
3321
3350
  if self._started:
3322
- # httpx.AsyncClient does not allow reentrancy so we will not either.
3323
- raise RuntimeError("The client cannot be started more than once.")
3351
+ # allow reentrancy
3352
+ return self
3324
3353
 
3325
3354
  self._loop = asyncio.get_running_loop()
3326
3355
  await self._exit_stack.__aenter__()
@@ -3351,6 +3380,10 @@ class PrefectClient:
3351
3380
  """
3352
3381
  Shutdown the client.
3353
3382
  """
3383
+
3384
+ self._context_stack -= 1
3385
+ if self._context_stack > 0:
3386
+ return
3354
3387
  self._closed = True
3355
3388
  return await self._exit_stack.__aexit__(*exc_info)
3356
3389
 
@@ -3394,9 +3427,10 @@ class SyncPrefectClient:
3394
3427
  self,
3395
3428
  api: Union[str, ASGIApp],
3396
3429
  *,
3397
- api_key: str = None,
3398
- api_version: str = None,
3430
+ api_key: Optional[str] = None,
3431
+ api_version: Optional[str] = None,
3399
3432
  httpx_settings: Optional[Dict[str, Any]] = None,
3433
+ server_type: Optional[ServerType] = None,
3400
3434
  ) -> None:
3401
3435
  httpx_settings = httpx_settings.copy() if httpx_settings else {}
3402
3436
  httpx_settings.setdefault("headers", {})
@@ -3416,6 +3450,7 @@ class SyncPrefectClient:
3416
3450
  httpx_settings["headers"].setdefault("Authorization", f"Bearer {api_key}")
3417
3451
 
3418
3452
  # Context management
3453
+ self._context_stack: int = 0
3419
3454
  self._ephemeral_app: Optional[ASGIApp] = None
3420
3455
  self.manage_lifespan = True
3421
3456
  self.server_type: ServerType
@@ -3454,11 +3489,14 @@ class SyncPrefectClient:
3454
3489
  # client will use a standard HTTP/1.1 connection instead.
3455
3490
  httpx_settings.setdefault("http2", PREFECT_API_ENABLE_HTTP2.value())
3456
3491
 
3457
- self.server_type = (
3458
- ServerType.CLOUD
3459
- if api.startswith(PREFECT_CLOUD_API_URL.value())
3460
- else ServerType.SERVER
3461
- )
3492
+ if server_type:
3493
+ self.server_type = server_type
3494
+ else:
3495
+ self.server_type = (
3496
+ ServerType.CLOUD
3497
+ if api.startswith(PREFECT_CLOUD_API_URL.value())
3498
+ else ServerType.SERVER
3499
+ )
3462
3500
 
3463
3501
  # Connect to an in-process application
3464
3502
  elif isinstance(api, ASGIApp):
@@ -3490,14 +3528,9 @@ class SyncPrefectClient:
3490
3528
  and PREFECT_CLIENT_CSRF_SUPPORT_ENABLED.value()
3491
3529
  )
3492
3530
 
3493
- if self.server_type == ServerType.EPHEMERAL:
3494
- self._client = PrefectHttpxSyncEphemeralClient(
3495
- api, base_url="http://ephemeral-prefect/api"
3496
- )
3497
- else:
3498
- self._client = PrefectHttpxSyncClient(
3499
- **httpx_settings, enable_csrf_support=enable_csrf_support
3500
- )
3531
+ self._client = PrefectHttpxSyncClient(
3532
+ **httpx_settings, enable_csrf_support=enable_csrf_support
3533
+ )
3501
3534
 
3502
3535
  # See https://www.python-httpx.org/advanced/#custom-transports
3503
3536
  #
@@ -3547,9 +3580,12 @@ class SyncPrefectClient:
3547
3580
  "Retrieve a new client with `get_client()` instead."
3548
3581
  )
3549
3582
 
3583
+ self._context_stack += 1
3584
+
3550
3585
  if self._started:
3551
- # httpx.Client does not allow reentrancy so we will not either.
3552
- raise RuntimeError("The client cannot be started more than once.")
3586
+ # allow reentrancy
3587
+ return self
3588
+
3553
3589
  self._client.__enter__()
3554
3590
  self._started = True
3555
3591
 
@@ -3559,6 +3595,9 @@ class SyncPrefectClient:
3559
3595
  """
3560
3596
  Shutdown the client.
3561
3597
  """
3598
+ self._context_stack -= 1
3599
+ if self._context_stack > 0:
3600
+ return
3562
3601
  self._closed = True
3563
3602
  self._client.__exit__(*exc_info)
3564
3603
 
@@ -3612,9 +3651,7 @@ class SyncPrefectClient:
3612
3651
  the ID of the flow in the backend
3613
3652
  """
3614
3653
  flow_data = FlowCreate(name=flow_name)
3615
- response = self._client.post(
3616
- "/flows/", json=flow_data.dict(json_compatible=True)
3617
- )
3654
+ response = self._client.post("/flows/", json=flow_data.model_dump(mode="json"))
3618
3655
 
3619
3656
  flow_id = response.json().get("id")
3620
3657
  if not flow_id:
@@ -3677,9 +3714,9 @@ class SyncPrefectClient:
3677
3714
  ),
3678
3715
  )
3679
3716
 
3680
- flow_run_create_json = flow_run_create.dict(json_compatible=True)
3717
+ flow_run_create_json = flow_run_create.model_dump(mode="json")
3681
3718
  response = self._client.post("/flow_runs/", json=flow_run_create_json)
3682
- flow_run = FlowRun.parse_obj(response.json())
3719
+ flow_run = FlowRun.model_validate(response.json())
3683
3720
 
3684
3721
  # Restore the parameters to the local objects to retain expectations about
3685
3722
  # Python objects
@@ -3687,6 +3724,61 @@ class SyncPrefectClient:
3687
3724
 
3688
3725
  return flow_run
3689
3726
 
3727
+ def update_flow_run(
3728
+ self,
3729
+ flow_run_id: UUID,
3730
+ flow_version: Optional[str] = None,
3731
+ parameters: Optional[dict] = None,
3732
+ name: Optional[str] = None,
3733
+ tags: Optional[Iterable[str]] = None,
3734
+ empirical_policy: Optional[FlowRunPolicy] = None,
3735
+ infrastructure_pid: Optional[str] = None,
3736
+ job_variables: Optional[dict] = None,
3737
+ ) -> httpx.Response:
3738
+ """
3739
+ Update a flow run's details.
3740
+
3741
+ Args:
3742
+ flow_run_id: The identifier for the flow run to update.
3743
+ flow_version: A new version string for the flow run.
3744
+ parameters: A dictionary of parameter values for the flow run. This will not
3745
+ be merged with any existing parameters.
3746
+ name: A new name for the flow run.
3747
+ empirical_policy: A new flow run orchestration policy. This will not be
3748
+ merged with any existing policy.
3749
+ tags: An iterable of new tags for the flow run. These will not be merged with
3750
+ any existing tags.
3751
+ infrastructure_pid: The id of flow run as returned by an
3752
+ infrastructure block.
3753
+
3754
+ Returns:
3755
+ an `httpx.Response` object from the PATCH request
3756
+ """
3757
+ params = {}
3758
+ if flow_version is not None:
3759
+ params["flow_version"] = flow_version
3760
+ if parameters is not None:
3761
+ params["parameters"] = parameters
3762
+ if name is not None:
3763
+ params["name"] = name
3764
+ if tags is not None:
3765
+ params["tags"] = tags
3766
+ if empirical_policy is not None:
3767
+ params["empirical_policy"] = empirical_policy.model_dump(
3768
+ mode="json", exclude_unset=True
3769
+ )
3770
+ if infrastructure_pid:
3771
+ params["infrastructure_pid"] = infrastructure_pid
3772
+ if job_variables is not None:
3773
+ params["job_variables"] = job_variables
3774
+
3775
+ flow_run_data = FlowRunUpdate(**params)
3776
+
3777
+ return self._client.patch(
3778
+ f"/flow_runs/{flow_run_id}",
3779
+ json=flow_run_data.model_dump(mode="json", exclude_unset=True),
3780
+ )
3781
+
3690
3782
  def read_flow_run(self, flow_run_id: UUID) -> FlowRun:
3691
3783
  """
3692
3784
  Query the Prefect API for a flow run by id.
@@ -3704,7 +3796,7 @@ class SyncPrefectClient:
3704
3796
  raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
3705
3797
  else:
3706
3798
  raise
3707
- return FlowRun.parse_obj(response.json())
3799
+ return FlowRun.model_validate(response.json())
3708
3800
 
3709
3801
  def read_flow_runs(
3710
3802
  self,
@@ -3716,7 +3808,7 @@ class SyncPrefectClient:
3716
3808
  work_pool_filter: WorkPoolFilter = None,
3717
3809
  work_queue_filter: WorkQueueFilter = None,
3718
3810
  sort: FlowRunSort = None,
3719
- limit: int = None,
3811
+ limit: Optional[int] = None,
3720
3812
  offset: int = 0,
3721
3813
  ) -> List[FlowRun]:
3722
3814
  """
@@ -3739,29 +3831,23 @@ class SyncPrefectClient:
3739
3831
  of the flow runs
3740
3832
  """
3741
3833
  body = {
3742
- "flows": flow_filter.dict(json_compatible=True) if flow_filter else None,
3834
+ "flows": flow_filter.model_dump(mode="json") if flow_filter else None,
3743
3835
  "flow_runs": (
3744
- flow_run_filter.dict(json_compatible=True, exclude_unset=True)
3836
+ flow_run_filter.model_dump(mode="json", exclude_unset=True)
3745
3837
  if flow_run_filter
3746
3838
  else None
3747
3839
  ),
3748
3840
  "task_runs": (
3749
- task_run_filter.dict(json_compatible=True) if task_run_filter else None
3841
+ task_run_filter.model_dump(mode="json") if task_run_filter else None
3750
3842
  ),
3751
3843
  "deployments": (
3752
- deployment_filter.dict(json_compatible=True)
3753
- if deployment_filter
3754
- else None
3844
+ deployment_filter.model_dump(mode="json") if deployment_filter else None
3755
3845
  ),
3756
3846
  "work_pools": (
3757
- work_pool_filter.dict(json_compatible=True)
3758
- if work_pool_filter
3759
- else None
3847
+ work_pool_filter.model_dump(mode="json") if work_pool_filter else None
3760
3848
  ),
3761
3849
  "work_pool_queues": (
3762
- work_queue_filter.dict(json_compatible=True)
3763
- if work_queue_filter
3764
- else None
3850
+ work_queue_filter.model_dump(mode="json") if work_queue_filter else None
3765
3851
  ),
3766
3852
  "sort": sort,
3767
3853
  "limit": limit,
@@ -3769,7 +3855,7 @@ class SyncPrefectClient:
3769
3855
  }
3770
3856
 
3771
3857
  response = self._client.post("/flow_runs/filter", json=body)
3772
- return pydantic.parse_obj_as(List[FlowRun], response.json())
3858
+ return pydantic.TypeAdapter(List[FlowRun]).validate_python(response.json())
3773
3859
 
3774
3860
  def set_flow_run_state(
3775
3861
  self,
@@ -3795,7 +3881,7 @@ class SyncPrefectClient:
3795
3881
  try:
3796
3882
  response = self._client.post(
3797
3883
  f"/flow_runs/{flow_run_id}/set_state",
3798
- json=dict(state=state_create.dict(json_compatible=True), force=force),
3884
+ json=dict(state=state_create.model_dump(mode="json"), force=force),
3799
3885
  )
3800
3886
  except httpx.HTTPStatusError as e:
3801
3887
  if e.response.status_code == status.HTTP_404_NOT_FOUND:
@@ -3803,13 +3889,28 @@ class SyncPrefectClient:
3803
3889
  else:
3804
3890
  raise
3805
3891
 
3806
- return OrchestrationResult.parse_obj(response.json())
3892
+ return OrchestrationResult.model_validate(response.json())
3893
+
3894
+ def set_flow_run_name(self, flow_run_id: UUID, name: str):
3895
+ flow_run_data = TaskRunUpdate(name=name)
3896
+ return self._client.patch(
3897
+ f"/flow_runs/{flow_run_id}",
3898
+ json=flow_run_data.model_dump(mode="json", exclude_unset=True),
3899
+ )
3900
+
3901
+ def set_task_run_name(self, task_run_id: UUID, name: str):
3902
+ task_run_data = TaskRunUpdate(name=name)
3903
+ return self._client.patch(
3904
+ f"/task_runs/{task_run_id}",
3905
+ json=task_run_data.model_dump(mode="json", exclude_unset=True),
3906
+ )
3807
3907
 
3808
3908
  def create_task_run(
3809
3909
  self,
3810
3910
  task: "TaskObject[P, R]",
3811
3911
  flow_run_id: Optional[UUID],
3812
3912
  dynamic_key: str,
3913
+ id: Optional[UUID] = None,
3813
3914
  name: Optional[str] = None,
3814
3915
  extra_tags: Optional[Iterable[str]] = None,
3815
3916
  state: Optional[prefect.states.State[R]] = None,
@@ -3833,6 +3934,8 @@ class SyncPrefectClient:
3833
3934
  task: The Task to run
3834
3935
  flow_run_id: The flow run id with which to associate the task run
3835
3936
  dynamic_key: A key unique to this particular run of a Task within the flow
3937
+ id: An optional ID for the task run. If not provided, one will be generated
3938
+ server-side.
3836
3939
  name: An optional name for the task run
3837
3940
  extra_tags: an optional list of extra tags to apply to the task run in
3838
3941
  addition to `task.tags`
@@ -3849,6 +3952,7 @@ class SyncPrefectClient:
3849
3952
  state = prefect.states.Pending()
3850
3953
 
3851
3954
  task_run_data = TaskRunCreate(
3955
+ id=id,
3852
3956
  name=name,
3853
3957
  flow_run_id=flow_run_id,
3854
3958
  task_key=task.task_key,
@@ -3864,10 +3968,10 @@ class SyncPrefectClient:
3864
3968
  task_inputs=task_inputs or {},
3865
3969
  )
3866
3970
 
3867
- response = self._client.post(
3868
- "/task_runs/", json=task_run_data.dict(json_compatible=True)
3869
- )
3870
- return TaskRun.parse_obj(response.json())
3971
+ content = task_run_data.model_dump_json(exclude={"id"} if id is None else None)
3972
+
3973
+ response = self._client.post("/task_runs/", content=content)
3974
+ return TaskRun.model_validate(response.json())
3871
3975
 
3872
3976
  def read_task_run(self, task_run_id: UUID) -> TaskRun:
3873
3977
  """
@@ -3879,8 +3983,14 @@ class SyncPrefectClient:
3879
3983
  Returns:
3880
3984
  a Task Run model representation of the task run
3881
3985
  """
3882
- response = self._client.get(f"/task_runs/{task_run_id}")
3883
- return TaskRun.parse_obj(response.json())
3986
+ try:
3987
+ response = self._client.get(f"/task_runs/{task_run_id}")
3988
+ return TaskRun.model_validate(response.json())
3989
+ except httpx.HTTPStatusError as e:
3990
+ if e.response.status_code == status.HTTP_404_NOT_FOUND:
3991
+ raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
3992
+ else:
3993
+ raise
3884
3994
 
3885
3995
  def read_task_runs(
3886
3996
  self,
@@ -3890,7 +4000,7 @@ class SyncPrefectClient:
3890
4000
  task_run_filter: TaskRunFilter = None,
3891
4001
  deployment_filter: DeploymentFilter = None,
3892
4002
  sort: TaskRunSort = None,
3893
- limit: int = None,
4003
+ limit: Optional[int] = None,
3894
4004
  offset: int = 0,
3895
4005
  ) -> List[TaskRun]:
3896
4006
  """
@@ -3911,26 +4021,24 @@ class SyncPrefectClient:
3911
4021
  of the task runs
3912
4022
  """
3913
4023
  body = {
3914
- "flows": flow_filter.dict(json_compatible=True) if flow_filter else None,
4024
+ "flows": flow_filter.model_dump(mode="json") if flow_filter else None,
3915
4025
  "flow_runs": (
3916
- flow_run_filter.dict(json_compatible=True, exclude_unset=True)
4026
+ flow_run_filter.model_dump(mode="json", exclude_unset=True)
3917
4027
  if flow_run_filter
3918
4028
  else None
3919
4029
  ),
3920
4030
  "task_runs": (
3921
- task_run_filter.dict(json_compatible=True) if task_run_filter else None
4031
+ task_run_filter.model_dump(mode="json") if task_run_filter else None
3922
4032
  ),
3923
4033
  "deployments": (
3924
- deployment_filter.dict(json_compatible=True)
3925
- if deployment_filter
3926
- else None
4034
+ deployment_filter.model_dump(mode="json") if deployment_filter else None
3927
4035
  ),
3928
4036
  "sort": sort,
3929
4037
  "limit": limit,
3930
4038
  "offset": offset,
3931
4039
  }
3932
4040
  response = self._client.post("/task_runs/filter", json=body)
3933
- return pydantic.parse_obj_as(List[TaskRun], response.json())
4041
+ return pydantic.TypeAdapter(List[TaskRun]).validate_python(response.json())
3934
4042
 
3935
4043
  def set_task_run_state(
3936
4044
  self,
@@ -3954,9 +4062,9 @@ class SyncPrefectClient:
3954
4062
  state_create.state_details.task_run_id = task_run_id
3955
4063
  response = self._client.post(
3956
4064
  f"/task_runs/{task_run_id}/set_state",
3957
- json=dict(state=state_create.dict(json_compatible=True), force=force),
4065
+ json=dict(state=state_create.model_dump(mode="json"), force=force),
3958
4066
  )
3959
- return OrchestrationResult.parse_obj(response.json())
4067
+ return OrchestrationResult.model_validate(response.json())
3960
4068
 
3961
4069
  def read_task_run_states(self, task_run_id: UUID) -> List[prefect.states.State]:
3962
4070
  """
@@ -3971,4 +4079,123 @@ class SyncPrefectClient:
3971
4079
  response = self._client.get(
3972
4080
  "/task_run_states/", params=dict(task_run_id=str(task_run_id))
3973
4081
  )
3974
- return pydantic.parse_obj_as(List[prefect.states.State], response.json())
4082
+ return pydantic.TypeAdapter(List[prefect.states.State]).validate_python(
4083
+ response.json()
4084
+ )
4085
+
4086
+ def read_deployment(
4087
+ self,
4088
+ deployment_id: UUID,
4089
+ ) -> DeploymentResponse:
4090
+ """
4091
+ Query the Prefect API for a deployment by id.
4092
+
4093
+ Args:
4094
+ deployment_id: the deployment ID of interest
4095
+
4096
+ Returns:
4097
+ a [Deployment model][prefect.client.schemas.objects.Deployment] representation of the deployment
4098
+ """
4099
+ try:
4100
+ response = self._client.get(f"/deployments/{deployment_id}")
4101
+ except httpx.HTTPStatusError as e:
4102
+ if e.response.status_code == status.HTTP_404_NOT_FOUND:
4103
+ raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
4104
+ else:
4105
+ raise
4106
+ return DeploymentResponse.model_validate(response.json())
4107
+
4108
+ def read_deployment_by_name(
4109
+ self,
4110
+ name: str,
4111
+ ) -> DeploymentResponse:
4112
+ """
4113
+ Query the Prefect API for a deployment by name.
4114
+
4115
+ Args:
4116
+ name: A deployed flow's name: <FLOW_NAME>/<DEPLOYMENT_NAME>
4117
+
4118
+ Raises:
4119
+ prefect.exceptions.ObjectNotFound: If request returns 404
4120
+ httpx.RequestError: If request fails
4121
+
4122
+ Returns:
4123
+ a Deployment model representation of the deployment
4124
+ """
4125
+ try:
4126
+ response = self._client.get(f"/deployments/name/{name}")
4127
+ except httpx.HTTPStatusError as e:
4128
+ if e.response.status_code == status.HTTP_404_NOT_FOUND:
4129
+ raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
4130
+ else:
4131
+ raise
4132
+
4133
+ return DeploymentResponse.model_validate(response.json())
4134
+
4135
+ def create_artifact(
4136
+ self,
4137
+ artifact: ArtifactCreate,
4138
+ ) -> Artifact:
4139
+ """
4140
+ Creates an artifact with the provided configuration.
4141
+
4142
+ Args:
4143
+ artifact: Desired configuration for the new artifact.
4144
+ Returns:
4145
+ Information about the newly created artifact.
4146
+ """
4147
+
4148
+ response = self._client.post(
4149
+ "/artifacts/",
4150
+ json=artifact.model_dump(mode="json", exclude_unset=True),
4151
+ )
4152
+
4153
+ return Artifact.model_validate(response.json())
4154
+
4155
+ def release_concurrency_slots(
4156
+ self, names: List[str], slots: int, occupancy_seconds: float
4157
+ ) -> httpx.Response:
4158
+ """
4159
+ Release concurrency slots for the specified limits.
4160
+
4161
+ Args:
4162
+ names (List[str]): A list of limit names for which to release slots.
4163
+ slots (int): The number of concurrency slots to release.
4164
+ occupancy_seconds (float): The duration in seconds that the slots
4165
+ were occupied.
4166
+
4167
+ Returns:
4168
+ httpx.Response: The HTTP response from the server.
4169
+ """
4170
+ return self._client.post(
4171
+ "/v2/concurrency_limits/decrement",
4172
+ json={
4173
+ "names": names,
4174
+ "slots": slots,
4175
+ "occupancy_seconds": occupancy_seconds,
4176
+ },
4177
+ )
4178
+
4179
+ def decrement_v1_concurrency_slots(
4180
+ self, names: List[str], occupancy_seconds: float, task_run_id: UUID
4181
+ ) -> httpx.Response:
4182
+ """
4183
+ Release the specified concurrency limits.
4184
+
4185
+ Args:
4186
+ names (List[str]): A list of limit names to decrement.
4187
+ occupancy_seconds (float): The duration in seconds that the slots
4188
+ were held.
4189
+ task_run_id (UUID): The task run ID that incremented the limits.
4190
+
4191
+ Returns:
4192
+ httpx.Response: The HTTP response from the server.
4193
+ """
4194
+ return self._client.post(
4195
+ "/concurrency_limits/decrement",
4196
+ json={
4197
+ "names": names,
4198
+ "occupancy_seconds": occupancy_seconds,
4199
+ "task_run_id": str(task_run_id),
4200
+ },
4201
+ )