prefect-client 3.1.5__py3-none-any.whl → 3.1.6__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 (93) hide show
  1. prefect/__init__.py +3 -0
  2. prefect/_internal/compatibility/migration.py +1 -1
  3. prefect/_internal/concurrency/api.py +52 -52
  4. prefect/_internal/concurrency/calls.py +59 -35
  5. prefect/_internal/concurrency/cancellation.py +34 -18
  6. prefect/_internal/concurrency/event_loop.py +7 -6
  7. prefect/_internal/concurrency/threads.py +41 -33
  8. prefect/_internal/concurrency/waiters.py +28 -21
  9. prefect/_internal/pydantic/v1_schema.py +2 -2
  10. prefect/_internal/pydantic/v2_schema.py +10 -9
  11. prefect/_internal/schemas/bases.py +9 -7
  12. prefect/_internal/schemas/validators.py +2 -1
  13. prefect/_version.py +3 -3
  14. prefect/automations.py +53 -47
  15. prefect/blocks/abstract.py +12 -10
  16. prefect/blocks/core.py +4 -2
  17. prefect/cache_policies.py +11 -11
  18. prefect/client/__init__.py +3 -1
  19. prefect/client/base.py +36 -37
  20. prefect/client/cloud.py +26 -19
  21. prefect/client/collections.py +2 -2
  22. prefect/client/orchestration.py +342 -273
  23. prefect/client/schemas/__init__.py +24 -0
  24. prefect/client/schemas/actions.py +123 -116
  25. prefect/client/schemas/objects.py +110 -81
  26. prefect/client/schemas/responses.py +18 -18
  27. prefect/client/schemas/schedules.py +136 -93
  28. prefect/client/subscriptions.py +28 -14
  29. prefect/client/utilities.py +32 -36
  30. prefect/concurrency/asyncio.py +6 -9
  31. prefect/concurrency/sync.py +35 -5
  32. prefect/context.py +39 -31
  33. prefect/deployments/flow_runs.py +3 -5
  34. prefect/docker/__init__.py +1 -1
  35. prefect/events/schemas/events.py +25 -20
  36. prefect/events/utilities.py +1 -2
  37. prefect/filesystems.py +3 -3
  38. prefect/flow_engine.py +61 -21
  39. prefect/flow_runs.py +3 -3
  40. prefect/flows.py +214 -170
  41. prefect/logging/configuration.py +1 -1
  42. prefect/logging/highlighters.py +1 -2
  43. prefect/logging/loggers.py +30 -20
  44. prefect/main.py +17 -24
  45. prefect/runner/runner.py +43 -21
  46. prefect/runner/server.py +30 -32
  47. prefect/runner/submit.py +3 -6
  48. prefect/runner/utils.py +6 -6
  49. prefect/runtime/flow_run.py +7 -0
  50. prefect/settings/constants.py +2 -2
  51. prefect/settings/legacy.py +1 -1
  52. prefect/settings/models/server/events.py +10 -0
  53. prefect/task_engine.py +72 -19
  54. prefect/task_runners.py +2 -2
  55. prefect/tasks.py +46 -33
  56. prefect/telemetry/bootstrap.py +15 -2
  57. prefect/telemetry/run_telemetry.py +107 -0
  58. prefect/transactions.py +14 -14
  59. prefect/types/__init__.py +1 -4
  60. prefect/utilities/_engine.py +96 -0
  61. prefect/utilities/annotations.py +25 -18
  62. prefect/utilities/asyncutils.py +126 -140
  63. prefect/utilities/callables.py +87 -78
  64. prefect/utilities/collections.py +278 -117
  65. prefect/utilities/compat.py +13 -21
  66. prefect/utilities/context.py +6 -5
  67. prefect/utilities/dispatch.py +23 -12
  68. prefect/utilities/dockerutils.py +33 -32
  69. prefect/utilities/engine.py +126 -239
  70. prefect/utilities/filesystem.py +18 -15
  71. prefect/utilities/hashing.py +10 -11
  72. prefect/utilities/importtools.py +40 -27
  73. prefect/utilities/math.py +9 -5
  74. prefect/utilities/names.py +3 -3
  75. prefect/utilities/processutils.py +121 -57
  76. prefect/utilities/pydantic.py +41 -36
  77. prefect/utilities/render_swagger.py +22 -12
  78. prefect/utilities/schema_tools/__init__.py +2 -1
  79. prefect/utilities/schema_tools/hydration.py +50 -43
  80. prefect/utilities/schema_tools/validation.py +52 -42
  81. prefect/utilities/services.py +13 -12
  82. prefect/utilities/templating.py +45 -45
  83. prefect/utilities/text.py +2 -1
  84. prefect/utilities/timeout.py +4 -4
  85. prefect/utilities/urls.py +9 -4
  86. prefect/utilities/visualization.py +46 -24
  87. prefect/variables.py +9 -8
  88. prefect/workers/base.py +15 -8
  89. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/METADATA +4 -2
  90. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/RECORD +93 -91
  91. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/LICENSE +0 -0
  92. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/WHEEL +0 -0
  93. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,7 @@ Utilities for creating and working with Prefect REST API schemas.
4
4
 
5
5
  import datetime
6
6
  import os
7
- from typing import Any, ClassVar, Optional, Set, TypeVar
7
+ from typing import Any, ClassVar, Generator, Optional, Set, TypeVar, cast
8
8
  from uuid import UUID, uuid4
9
9
 
10
10
  import pendulum
@@ -33,7 +33,7 @@ class PrefectBaseModel(BaseModel):
33
33
 
34
34
  _reset_fields: ClassVar[Set[str]] = set()
35
35
 
36
- model_config = ConfigDict(
36
+ model_config: ClassVar[ConfigDict] = ConfigDict(
37
37
  ser_json_timedelta="float",
38
38
  defer_build=True,
39
39
  extra=(
@@ -58,7 +58,7 @@ class PrefectBaseModel(BaseModel):
58
58
  else:
59
59
  return copy_dict == other
60
60
 
61
- def __rich_repr__(self):
61
+ def __rich_repr__(self) -> Generator[tuple[str, Any, Any], None, None]:
62
62
  # Display all of the fields in the model if they differ from the default value
63
63
  for name, field in self.model_fields.items():
64
64
  value = getattr(self, name)
@@ -71,9 +71,11 @@ class PrefectBaseModel(BaseModel):
71
71
  and name == "timestamp"
72
72
  and value
73
73
  ):
74
- value = pendulum.instance(value).isoformat()
74
+ value = cast(pendulum.DateTime, pendulum.instance(value)).isoformat()
75
75
  elif isinstance(field.annotation, datetime.datetime) and value:
76
- value = pendulum.instance(value).diff_for_humans()
76
+ value = cast(
77
+ pendulum.DateTime, pendulum.instance(value)
78
+ ).diff_for_humans()
77
79
 
78
80
  yield name, value, field.get_default()
79
81
 
@@ -113,11 +115,11 @@ class ObjectBaseModel(IDBaseModel):
113
115
  """
114
116
 
115
117
  _reset_fields: ClassVar[Set[str]] = {"id", "created", "updated"}
116
- model_config = ConfigDict(from_attributes=True)
118
+ model_config: ClassVar[ConfigDict] = ConfigDict(from_attributes=True)
117
119
 
118
120
  created: Optional[DateTime] = Field(default=None, repr=False)
119
121
  updated: Optional[DateTime] = Field(default=None, repr=False)
120
122
 
121
123
 
122
124
  class ActionBaseModel(PrefectBaseModel):
123
- model_config: ConfigDict = ConfigDict(extra="forbid")
125
+ model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
@@ -13,6 +13,7 @@ import warnings
13
13
  from copy import copy
14
14
  from pathlib import Path
15
15
  from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Tuple, Union
16
+ from uuid import UUID
16
17
 
17
18
  import jsonschema
18
19
  import pendulum
@@ -653,7 +654,7 @@ def validate_message_template_variables(v: Optional[str]) -> Optional[str]:
653
654
  return v
654
655
 
655
656
 
656
- def validate_default_queue_id_not_none(v: Optional[str]) -> Optional[str]:
657
+ def validate_default_queue_id_not_none(v: Optional[UUID]) -> UUID:
657
658
  if v is None:
658
659
  raise ValueError(
659
660
  "`default_queue_id` is a required field. If you are "
prefect/_version.py CHANGED
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2024-12-02T18:57:11-0600",
11
+ "date": "2024-12-11T10:46:23-0500",
12
12
  "dirty": true,
13
13
  "error": null,
14
- "full-revisionid": "3c06654ec8be4f9e9bf5c304814f163b7727d28e",
15
- "version": "3.1.5"
14
+ "full-revisionid": "6bfce9e87c6e7e3e44c65d8b5ecc6ab156f2c7c8",
15
+ "version": "3.1.6"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
prefect/automations.py CHANGED
@@ -1,10 +1,10 @@
1
- from typing import Optional
1
+ from typing import Optional, Type
2
2
  from uuid import UUID
3
3
 
4
4
  from pydantic import Field
5
5
  from typing_extensions import Self
6
6
 
7
- from prefect.client.utilities import get_or_create_client
7
+ from prefect.client.orchestration import get_client
8
8
  from prefect.events.actions import (
9
9
  CallWebhook,
10
10
  CancelFlowRun,
@@ -99,10 +99,10 @@ class Automation(AutomationCore):
99
99
  )
100
100
  created_automation = auto_to_create.create()
101
101
  """
102
- client, _ = get_or_create_client()
103
- automation = AutomationCore(**self.model_dump(exclude={"id"}))
104
- self.id = await client.create_automation(automation=automation)
105
- return self
102
+ async with get_client() as client:
103
+ automation = AutomationCore(**self.model_dump(exclude={"id"}))
104
+ self.id = await client.create_automation(automation=automation)
105
+ return self
106
106
 
107
107
  @sync_compatible
108
108
  async def update(self: Self):
@@ -112,15 +112,16 @@ class Automation(AutomationCore):
112
112
  auto.name = "new name"
113
113
  auto.update()
114
114
  """
115
-
116
- client, _ = get_or_create_client()
117
- automation = AutomationCore(**self.model_dump(exclude={"id", "owner_resource"}))
118
- await client.update_automation(automation_id=self.id, automation=automation)
115
+ async with get_client() as client:
116
+ automation = AutomationCore(
117
+ **self.model_dump(exclude={"id", "owner_resource"})
118
+ )
119
+ await client.update_automation(automation_id=self.id, automation=automation)
119
120
 
120
121
  @classmethod
121
122
  @sync_compatible
122
123
  async def read(
123
- cls: Self, id: Optional[UUID] = None, name: Optional[str] = None
124
+ cls: Type[Self], id: Optional[UUID] = None, name: Optional[str] = None
124
125
  ) -> Self:
125
126
  """
126
127
  Read an automation by ID or name.
@@ -134,20 +135,25 @@ class Automation(AutomationCore):
134
135
  raise ValueError("Only one of id or name can be provided")
135
136
  if not id and not name:
136
137
  raise ValueError("One of id or name must be provided")
137
- client, _ = get_or_create_client()
138
- if id:
139
- try:
140
- automation = await client.read_automation(automation_id=id)
141
- except PrefectHTTPStatusError as exc:
142
- if exc.response.status_code == 404:
138
+ async with get_client() as client:
139
+ if id:
140
+ try:
141
+ automation = await client.read_automation(automation_id=id)
142
+ except PrefectHTTPStatusError as exc:
143
+ if exc.response.status_code == 404:
144
+ raise ValueError(f"Automation with ID {id!r} not found")
145
+ raise
146
+ if automation is None:
143
147
  raise ValueError(f"Automation with ID {id!r} not found")
144
- return Automation(**automation.model_dump())
145
- else:
146
- automation = await client.read_automations_by_name(name=name)
147
- if len(automation) > 0:
148
- return Automation(**automation[0].model_dump()) if automation else None
148
+ return Automation(**automation.model_dump())
149
149
  else:
150
- raise ValueError(f"Automation with name {name!r} not found")
150
+ automation = await client.read_automations_by_name(name=name)
151
+ if len(automation) > 0:
152
+ return (
153
+ Automation(**automation[0].model_dump()) if automation else None
154
+ )
155
+ else:
156
+ raise ValueError(f"Automation with name {name!r} not found")
151
157
 
152
158
  @sync_compatible
153
159
  async def delete(self: Self) -> bool:
@@ -155,14 +161,14 @@ class Automation(AutomationCore):
155
161
  auto = Automation.read(id = 123)
156
162
  auto.delete()
157
163
  """
158
- try:
159
- client, _ = get_or_create_client()
160
- await client.delete_automation(self.id)
161
- return True
162
- except PrefectHTTPStatusError as exc:
163
- if exc.response.status_code == 404:
164
- return False
165
- raise
164
+ async with get_client() as client:
165
+ try:
166
+ await client.delete_automation(self.id)
167
+ return True
168
+ except PrefectHTTPStatusError as exc:
169
+ if exc.response.status_code == 404:
170
+ return False
171
+ raise
166
172
 
167
173
  @sync_compatible
168
174
  async def disable(self: Self) -> bool:
@@ -171,14 +177,14 @@ class Automation(AutomationCore):
171
177
  auto = Automation.read(id = 123)
172
178
  auto.disable()
173
179
  """
174
- try:
175
- client, _ = get_or_create_client()
176
- await client.pause_automation(self.id)
177
- return True
178
- except PrefectHTTPStatusError as exc:
179
- if exc.response.status_code == 404:
180
- return False
181
- raise
180
+ async with get_client() as client:
181
+ try:
182
+ await client.pause_automation(self.id)
183
+ return True
184
+ except PrefectHTTPStatusError as exc:
185
+ if exc.response.status_code == 404:
186
+ return False
187
+ raise
182
188
 
183
189
  @sync_compatible
184
190
  async def enable(self: Self) -> bool:
@@ -187,11 +193,11 @@ class Automation(AutomationCore):
187
193
  auto = Automation.read(id = 123)
188
194
  auto.enable()
189
195
  """
190
- try:
191
- client, _ = get_or_create_client()
192
- await client.resume_automation("asd")
193
- return True
194
- except PrefectHTTPStatusError as exc:
195
- if exc.response.status_code == 404:
196
- return False
197
- raise
196
+ async with get_client() as client:
197
+ try:
198
+ await client.resume_automation(self.id)
199
+ return True
200
+ except PrefectHTTPStatusError as exc:
201
+ if exc.response.status_code == 404:
202
+ return False
203
+ raise
@@ -1,6 +1,6 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from contextlib import contextmanager
3
- from logging import Logger
3
+ from logging import Logger, LoggerAdapter
4
4
  from pathlib import Path
5
5
  from typing import (
6
6
  Any,
@@ -15,7 +15,7 @@ from typing import (
15
15
  Union,
16
16
  )
17
17
 
18
- from typing_extensions import Self
18
+ from typing_extensions import Self, TypeAlias
19
19
 
20
20
  from prefect.blocks.core import Block
21
21
  from prefect.exceptions import MissingContextError
@@ -23,6 +23,8 @@ from prefect.logging.loggers import get_logger, get_run_logger
23
23
 
24
24
  T = TypeVar("T")
25
25
 
26
+ LoggerOrAdapter: TypeAlias = Union[Logger, LoggerAdapter]
27
+
26
28
 
27
29
  class CredentialsBlock(Block, ABC):
28
30
  """
@@ -34,7 +36,7 @@ class CredentialsBlock(Block, ABC):
34
36
  """
35
37
 
36
38
  @property
37
- def logger(self) -> Logger:
39
+ def logger(self) -> LoggerOrAdapter:
38
40
  """
39
41
  Returns a logger based on whether the CredentialsBlock
40
42
  is called from within a flow or task run context.
@@ -73,10 +75,10 @@ class NotificationBlock(Block, ABC):
73
75
  """
74
76
 
75
77
  _block_schema_capabilities = ["notify"]
76
- _events_excluded_methods = Block._events_excluded_methods.default + ["notify"]
78
+ _events_excluded_methods = Block._events_excluded_methods + ["notify"]
77
79
 
78
80
  @property
79
- def logger(self) -> Logger:
81
+ def logger(self) -> LoggerOrAdapter:
80
82
  """
81
83
  Returns a logger based on whether the NotificationBlock
82
84
  is called from within a flow or task run context.
@@ -123,7 +125,7 @@ class JobRun(ABC, Generic[T]): # not a block
123
125
  """
124
126
 
125
127
  @property
126
- def logger(self) -> Logger:
128
+ def logger(self) -> LoggerOrAdapter:
127
129
  """
128
130
  Returns a logger based on whether the JobRun
129
131
  is called from within a flow or task run context.
@@ -158,7 +160,7 @@ class JobBlock(Block, ABC):
158
160
  """
159
161
 
160
162
  @property
161
- def logger(self) -> Logger:
163
+ def logger(self) -> LoggerOrAdapter:
162
164
  """
163
165
  Returns a logger based on whether the JobBlock
164
166
  is called from within a flow or task run context.
@@ -202,7 +204,7 @@ class DatabaseBlock(Block, ABC):
202
204
  """
203
205
 
204
206
  @property
205
- def logger(self) -> Logger:
207
+ def logger(self) -> LoggerOrAdapter:
206
208
  """
207
209
  Returns a logger based on whether the DatabaseBlock
208
210
  is called from within a flow or task run context.
@@ -337,7 +339,7 @@ class ObjectStorageBlock(Block, ABC):
337
339
  """
338
340
 
339
341
  @property
340
- def logger(self) -> Logger:
342
+ def logger(self) -> LoggerOrAdapter:
341
343
  """
342
344
  Returns a logger based on whether the ObjectStorageBlock
343
345
  is called from within a flow or task run context.
@@ -469,7 +471,7 @@ class SecretBlock(Block, ABC):
469
471
  """
470
472
 
471
473
  @property
472
- def logger(self) -> Logger:
474
+ def logger(self) -> LoggerOrAdapter:
473
475
  """
474
476
  Returns a logger based on whether the SecretBlock
475
477
  is called from within a flow or task run context.
prefect/blocks/core.py CHANGED
@@ -326,7 +326,9 @@ class Block(BaseModel, ABC):
326
326
 
327
327
  # Exclude `save` as it uses the `sync_compatible` decorator and needs to be
328
328
  # decorated directly.
329
- _events_excluded_methods = ["block_initialization", "save", "dict"]
329
+ _events_excluded_methods: ClassVar[List[str]] = PrivateAttr(
330
+ default=["block_initialization", "save", "dict"]
331
+ )
330
332
 
331
333
  @classmethod
332
334
  def __dispatch_key__(cls):
@@ -1229,7 +1231,7 @@ class Block(BaseModel, ABC):
1229
1231
  """
1230
1232
  block_type_slug = kwargs.pop("block_type_slug", None)
1231
1233
  if block_type_slug:
1232
- subcls = lookup_type(cls, dispatch_key=block_type_slug)
1234
+ subcls = cls.get_block_class_from_key(block_type_slug)
1233
1235
  return super().__new__(subcls)
1234
1236
  else:
1235
1237
  return super().__new__(cls)
prefect/cache_policies.py CHANGED
@@ -75,12 +75,12 @@ class CachePolicy:
75
75
  task_ctx: TaskRunContext,
76
76
  inputs: Dict[str, Any],
77
77
  flow_parameters: Dict[str, Any],
78
- **kwargs,
78
+ **kwargs: Any,
79
79
  ) -> Optional[str]:
80
80
  raise NotImplementedError
81
81
 
82
82
  def __sub__(self, other: str) -> "CachePolicy":
83
- if not isinstance(other, str):
83
+ if not isinstance(other, str): # type: ignore[reportUnnecessaryIsInstance]
84
84
  raise TypeError("Can only subtract strings from key policies.")
85
85
  new = Inputs(exclude=[other])
86
86
  return CompoundCachePolicy(policies=[self, new])
@@ -140,7 +140,7 @@ class CacheKeyFnPolicy(CachePolicy):
140
140
  task_ctx: TaskRunContext,
141
141
  inputs: Dict[str, Any],
142
142
  flow_parameters: Dict[str, Any],
143
- **kwargs,
143
+ **kwargs: Any,
144
144
  ) -> Optional[str]:
145
145
  if self.cache_key_fn:
146
146
  return self.cache_key_fn(task_ctx, inputs)
@@ -162,9 +162,9 @@ class CompoundCachePolicy(CachePolicy):
162
162
  task_ctx: TaskRunContext,
163
163
  inputs: Dict[str, Any],
164
164
  flow_parameters: Dict[str, Any],
165
- **kwargs,
165
+ **kwargs: Any,
166
166
  ) -> Optional[str]:
167
- keys = []
167
+ keys: list[str] = []
168
168
  for policy in self.policies:
169
169
  policy_key = policy.compute_key(
170
170
  task_ctx=task_ctx,
@@ -191,7 +191,7 @@ class _None(CachePolicy):
191
191
  task_ctx: TaskRunContext,
192
192
  inputs: Dict[str, Any],
193
193
  flow_parameters: Dict[str, Any],
194
- **kwargs,
194
+ **kwargs: Any,
195
195
  ) -> Optional[str]:
196
196
  return None
197
197
 
@@ -211,7 +211,7 @@ class TaskSource(CachePolicy):
211
211
  task_ctx: TaskRunContext,
212
212
  inputs: Optional[Dict[str, Any]],
213
213
  flow_parameters: Optional[Dict[str, Any]],
214
- **kwargs,
214
+ **kwargs: Any,
215
215
  ) -> Optional[str]:
216
216
  if not task_ctx:
217
217
  return None
@@ -238,7 +238,7 @@ class FlowParameters(CachePolicy):
238
238
  task_ctx: TaskRunContext,
239
239
  inputs: Dict[str, Any],
240
240
  flow_parameters: Dict[str, Any],
241
- **kwargs,
241
+ **kwargs: Any,
242
242
  ) -> Optional[str]:
243
243
  if not flow_parameters:
244
244
  return None
@@ -257,7 +257,7 @@ class RunId(CachePolicy):
257
257
  task_ctx: TaskRunContext,
258
258
  inputs: Dict[str, Any],
259
259
  flow_parameters: Dict[str, Any],
260
- **kwargs,
260
+ **kwargs: Any,
261
261
  ) -> Optional[str]:
262
262
  if not task_ctx:
263
263
  return None
@@ -280,7 +280,7 @@ class Inputs(CachePolicy):
280
280
  task_ctx: TaskRunContext,
281
281
  inputs: Dict[str, Any],
282
282
  flow_parameters: Dict[str, Any],
283
- **kwargs,
283
+ **kwargs: Any,
284
284
  ) -> Optional[str]:
285
285
  hashed_inputs = {}
286
286
  inputs = inputs or {}
@@ -307,7 +307,7 @@ class Inputs(CachePolicy):
307
307
  raise ValueError(msg) from exc
308
308
 
309
309
  def __sub__(self, other: str) -> "CachePolicy":
310
- if not isinstance(other, str):
310
+ if not isinstance(other, str): # type: ignore[reportUnnecessaryIsInstance]
311
311
  raise TypeError("Can only subtract strings from key policies.")
312
312
  return Inputs(exclude=self.exclude + [other])
313
313
 
@@ -16,6 +16,8 @@ $ python -m asyncio
16
16
  </div>
17
17
  """
18
18
 
19
+ from collections.abc import Callable
20
+ from typing import Any
19
21
  from prefect._internal.compatibility.migration import getattr_migration
20
22
 
21
- __getattr__ = getattr_migration(__name__)
23
+ __getattr__: Callable[[str], Any] = getattr_migration(__name__)
prefect/client/base.py CHANGED
@@ -4,22 +4,11 @@ import threading
4
4
  import time
5
5
  import uuid
6
6
  from collections import defaultdict
7
+ from collections.abc import AsyncGenerator, Awaitable, MutableMapping
7
8
  from contextlib import asynccontextmanager
8
9
  from datetime import datetime, timezone
9
- from typing import (
10
- Any,
11
- AsyncGenerator,
12
- Awaitable,
13
- Callable,
14
- Dict,
15
- MutableMapping,
16
- Optional,
17
- Protocol,
18
- Set,
19
- Tuple,
20
- Type,
21
- runtime_checkable,
22
- )
10
+ from logging import Logger
11
+ from typing import TYPE_CHECKING, Any, Callable, Optional, Protocol, runtime_checkable
23
12
 
24
13
  import anyio
25
14
  import httpx
@@ -46,14 +35,14 @@ from prefect.utilities.math import bounded_poisson_interval, clamped_poisson_int
46
35
 
47
36
  # Datastores for lifespan management, keys should be a tuple of thread and app
48
37
  # identities.
49
- APP_LIFESPANS: Dict[Tuple[int, int], LifespanManager] = {}
50
- APP_LIFESPANS_REF_COUNTS: Dict[Tuple[int, int], int] = {}
38
+ APP_LIFESPANS: dict[tuple[int, int], LifespanManager] = {}
39
+ APP_LIFESPANS_REF_COUNTS: dict[tuple[int, int], int] = {}
51
40
  # Blocks concurrent access to the above dicts per thread. The index should be the thread
52
41
  # identity.
53
- APP_LIFESPANS_LOCKS: Dict[int, anyio.Lock] = defaultdict(anyio.Lock)
42
+ APP_LIFESPANS_LOCKS: dict[int, anyio.Lock] = defaultdict(anyio.Lock)
54
43
 
55
44
 
56
- logger = get_logger("client")
45
+ logger: Logger = get_logger("client")
57
46
 
58
47
 
59
48
  # Define ASGI application types for type checking
@@ -161,7 +150,7 @@ class PrefectResponse(httpx.Response):
161
150
  Provides more informative error messages.
162
151
  """
163
152
 
164
- def raise_for_status(self) -> None:
153
+ def raise_for_status(self) -> Response:
165
154
  """
166
155
  Raise an exception if the response contains an HTTPStatusError.
167
156
 
@@ -174,9 +163,9 @@ class PrefectResponse(httpx.Response):
174
163
  raise PrefectHTTPStatusError.from_httpx_error(exc) from exc.__cause__
175
164
 
176
165
  @classmethod
177
- def from_httpx_response(cls: Type[Self], response: httpx.Response) -> Self:
166
+ def from_httpx_response(cls: type[Self], response: httpx.Response) -> Response:
178
167
  """
179
- Create a `PrefectReponse` from an `httpx.Response`.
168
+ Create a `PrefectResponse` from an `httpx.Response`.
180
169
 
181
170
  By changing the `__class__` attribute of the Response, we change the method
182
171
  resolution order to look for methods defined in PrefectResponse, while leaving
@@ -200,10 +189,10 @@ class PrefectHttpxAsyncClient(httpx.AsyncClient):
200
189
 
201
190
  def __init__(
202
191
  self,
203
- *args,
192
+ *args: Any,
204
193
  enable_csrf_support: bool = False,
205
194
  raise_on_all_errors: bool = True,
206
- **kwargs,
195
+ **kwargs: Any,
207
196
  ):
208
197
  self.enable_csrf_support: bool = enable_csrf_support
209
198
  self.csrf_token: Optional[str] = None
@@ -222,10 +211,10 @@ class PrefectHttpxAsyncClient(httpx.AsyncClient):
222
211
  self,
223
212
  request: Request,
224
213
  send: Callable[[Request], Awaitable[Response]],
225
- send_args: Tuple,
226
- send_kwargs: Dict,
227
- retry_codes: Set[int] = set(),
228
- retry_exceptions: Tuple[Exception, ...] = tuple(),
214
+ send_args: tuple[Any, ...],
215
+ send_kwargs: dict[str, Any],
216
+ retry_codes: set[int] = set(),
217
+ retry_exceptions: tuple[type[Exception], ...] = tuple(),
229
218
  ):
230
219
  """
231
220
  Send a request and retry it if it fails.
@@ -240,6 +229,11 @@ class PrefectHttpxAsyncClient(httpx.AsyncClient):
240
229
  try_count = 0
241
230
  response = None
242
231
 
232
+ if TYPE_CHECKING:
233
+ # older httpx versions type method as str | bytes | Unknown
234
+ # but in reality it is always a string.
235
+ assert isinstance(request.method, str) # type: ignore
236
+
243
237
  is_change_request = request.method.lower() in {"post", "put", "patch", "delete"}
244
238
 
245
239
  if self.enable_csrf_support and is_change_request:
@@ -297,7 +291,7 @@ class PrefectHttpxAsyncClient(httpx.AsyncClient):
297
291
  if exc_info
298
292
  else (
299
293
  "Received response with retryable status code"
300
- f" {response.status_code}. "
294
+ f" {response.status_code if response else 'unknown'}. "
301
295
  )
302
296
  )
303
297
  + f"Another attempt will be made in {retry_seconds}s. "
@@ -314,7 +308,7 @@ class PrefectHttpxAsyncClient(httpx.AsyncClient):
314
308
  # We ran out of retries, return the failed response
315
309
  return response
316
310
 
317
- async def send(self, request: Request, *args, **kwargs) -> Response:
311
+ async def send(self, request: Request, *args: Any, **kwargs: Any) -> Response:
318
312
  """
319
313
  Send a request with automatic retry behavior for the following status codes:
320
314
 
@@ -414,10 +408,10 @@ class PrefectHttpxSyncClient(httpx.Client):
414
408
 
415
409
  def __init__(
416
410
  self,
417
- *args,
411
+ *args: Any,
418
412
  enable_csrf_support: bool = False,
419
413
  raise_on_all_errors: bool = True,
420
- **kwargs,
414
+ **kwargs: Any,
421
415
  ):
422
416
  self.enable_csrf_support: bool = enable_csrf_support
423
417
  self.csrf_token: Optional[str] = None
@@ -436,10 +430,10 @@ class PrefectHttpxSyncClient(httpx.Client):
436
430
  self,
437
431
  request: Request,
438
432
  send: Callable[[Request], Response],
439
- send_args: Tuple,
440
- send_kwargs: Dict,
441
- retry_codes: Set[int] = set(),
442
- retry_exceptions: Tuple[Exception, ...] = tuple(),
433
+ send_args: tuple[Any, ...],
434
+ send_kwargs: dict[str, Any],
435
+ retry_codes: set[int] = set(),
436
+ retry_exceptions: tuple[type[Exception], ...] = tuple(),
443
437
  ):
444
438
  """
445
439
  Send a request and retry it if it fails.
@@ -454,6 +448,11 @@ class PrefectHttpxSyncClient(httpx.Client):
454
448
  try_count = 0
455
449
  response = None
456
450
 
451
+ if TYPE_CHECKING:
452
+ # older httpx versions type method as str | bytes | Unknown
453
+ # but in reality it is always a string.
454
+ assert isinstance(request.method, str) # type: ignore
455
+
457
456
  is_change_request = request.method.lower() in {"post", "put", "patch", "delete"}
458
457
 
459
458
  if self.enable_csrf_support and is_change_request:
@@ -511,7 +510,7 @@ class PrefectHttpxSyncClient(httpx.Client):
511
510
  if exc_info
512
511
  else (
513
512
  "Received response with retryable status code"
514
- f" {response.status_code}. "
513
+ f" {response.status_code if response else 'unknown'}. "
515
514
  )
516
515
  )
517
516
  + f"Another attempt will be made in {retry_seconds}s. "
@@ -528,7 +527,7 @@ class PrefectHttpxSyncClient(httpx.Client):
528
527
  # We ran out of retries, return the failed response
529
528
  return response
530
529
 
531
- def send(self, request: Request, *args, **kwargs) -> Response:
530
+ def send(self, request: Request, *args: Any, **kwargs: Any) -> Response:
532
531
  """
533
532
  Send a request with automatic retry behavior for the following status codes:
534
533