prefect-client 3.1.12__py3-none-any.whl → 3.1.13__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 (105) hide show
  1. prefect/_experimental/sla/client.py +53 -27
  2. prefect/_experimental/sla/objects.py +10 -2
  3. prefect/_internal/concurrency/services.py +2 -2
  4. prefect/_internal/concurrency/threads.py +6 -0
  5. prefect/_internal/retries.py +6 -3
  6. prefect/_internal/schemas/validators.py +6 -4
  7. prefect/_version.py +3 -3
  8. prefect/artifacts.py +4 -1
  9. prefect/automations.py +1 -1
  10. prefect/blocks/abstract.py +5 -2
  11. prefect/blocks/notifications.py +1 -0
  12. prefect/cache_policies.py +20 -20
  13. prefect/client/utilities.py +3 -3
  14. prefect/deployments/base.py +7 -4
  15. prefect/deployments/flow_runs.py +5 -1
  16. prefect/deployments/runner.py +6 -11
  17. prefect/deployments/steps/core.py +1 -1
  18. prefect/deployments/steps/pull.py +8 -3
  19. prefect/deployments/steps/utility.py +2 -2
  20. prefect/docker/docker_image.py +13 -9
  21. prefect/engine.py +19 -10
  22. prefect/events/cli/automations.py +4 -4
  23. prefect/events/clients.py +17 -14
  24. prefect/events/schemas/automations.py +12 -8
  25. prefect/events/schemas/events.py +5 -1
  26. prefect/events/worker.py +1 -1
  27. prefect/filesystems.py +1 -1
  28. prefect/flow_engine.py +17 -9
  29. prefect/flows.py +118 -73
  30. prefect/futures.py +14 -7
  31. prefect/infrastructure/provisioners/__init__.py +2 -0
  32. prefect/infrastructure/provisioners/cloud_run.py +4 -4
  33. prefect/infrastructure/provisioners/coiled.py +249 -0
  34. prefect/infrastructure/provisioners/container_instance.py +4 -3
  35. prefect/infrastructure/provisioners/ecs.py +55 -43
  36. prefect/infrastructure/provisioners/modal.py +5 -4
  37. prefect/input/actions.py +5 -1
  38. prefect/input/run_input.py +157 -43
  39. prefect/logging/configuration.py +3 -3
  40. prefect/logging/filters.py +2 -2
  41. prefect/logging/formatters.py +15 -11
  42. prefect/logging/handlers.py +24 -14
  43. prefect/logging/highlighters.py +5 -5
  44. prefect/logging/loggers.py +28 -18
  45. prefect/main.py +3 -1
  46. prefect/results.py +166 -86
  47. prefect/runner/runner.py +34 -27
  48. prefect/runner/server.py +3 -1
  49. prefect/runner/storage.py +18 -18
  50. prefect/runner/submit.py +19 -12
  51. prefect/runtime/deployment.py +15 -8
  52. prefect/runtime/flow_run.py +19 -6
  53. prefect/runtime/task_run.py +7 -3
  54. prefect/settings/base.py +17 -7
  55. prefect/settings/legacy.py +4 -4
  56. prefect/settings/models/api.py +4 -3
  57. prefect/settings/models/cli.py +4 -3
  58. prefect/settings/models/client.py +7 -4
  59. prefect/settings/models/cloud.py +4 -3
  60. prefect/settings/models/deployments.py +4 -3
  61. prefect/settings/models/experiments.py +4 -3
  62. prefect/settings/models/flows.py +4 -3
  63. prefect/settings/models/internal.py +4 -3
  64. prefect/settings/models/logging.py +8 -6
  65. prefect/settings/models/results.py +4 -3
  66. prefect/settings/models/root.py +11 -16
  67. prefect/settings/models/runner.py +8 -5
  68. prefect/settings/models/server/api.py +6 -3
  69. prefect/settings/models/server/database.py +120 -25
  70. prefect/settings/models/server/deployments.py +4 -3
  71. prefect/settings/models/server/ephemeral.py +7 -4
  72. prefect/settings/models/server/events.py +6 -3
  73. prefect/settings/models/server/flow_run_graph.py +4 -3
  74. prefect/settings/models/server/root.py +4 -3
  75. prefect/settings/models/server/services.py +15 -12
  76. prefect/settings/models/server/tasks.py +7 -4
  77. prefect/settings/models/server/ui.py +4 -3
  78. prefect/settings/models/tasks.py +10 -5
  79. prefect/settings/models/testing.py +4 -3
  80. prefect/settings/models/worker.py +7 -4
  81. prefect/settings/profiles.py +13 -12
  82. prefect/settings/sources.py +20 -19
  83. prefect/states.py +17 -13
  84. prefect/task_engine.py +43 -33
  85. prefect/task_runners.py +35 -23
  86. prefect/task_runs.py +20 -11
  87. prefect/task_worker.py +12 -7
  88. prefect/tasks.py +30 -24
  89. prefect/telemetry/bootstrap.py +4 -1
  90. prefect/telemetry/run_telemetry.py +15 -13
  91. prefect/transactions.py +3 -3
  92. prefect/types/__init__.py +3 -1
  93. prefect/utilities/_deprecated.py +38 -0
  94. prefect/utilities/engine.py +11 -4
  95. prefect/utilities/filesystem.py +2 -2
  96. prefect/utilities/generics.py +1 -1
  97. prefect/utilities/pydantic.py +21 -36
  98. prefect/workers/base.py +52 -30
  99. prefect/workers/process.py +20 -15
  100. prefect/workers/server.py +4 -5
  101. {prefect_client-3.1.12.dist-info → prefect_client-3.1.13.dist-info}/METADATA +2 -2
  102. {prefect_client-3.1.12.dist-info → prefect_client-3.1.13.dist-info}/RECORD +105 -103
  103. {prefect_client-3.1.12.dist-info → prefect_client-3.1.13.dist-info}/LICENSE +0 -0
  104. {prefect_client-3.1.12.dist-info → prefect_client-3.1.13.dist-info}/WHEEL +0 -0
  105. {prefect_client-3.1.12.dist-info → prefect_client-3.1.13.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,249 @@
1
+ import importlib
2
+ import shlex
3
+ import sys
4
+ from copy import deepcopy
5
+ from types import ModuleType
6
+ from typing import TYPE_CHECKING, Any, Dict, Optional
7
+
8
+ from anyio import run_process
9
+ from rich.console import Console
10
+ from rich.progress import Progress, SpinnerColumn, TextColumn
11
+ from rich.prompt import Confirm
12
+
13
+ from prefect.client.schemas.actions import BlockDocumentCreate
14
+ from prefect.client.schemas.objects import BlockDocument
15
+ from prefect.client.utilities import inject_client
16
+ from prefect.exceptions import ObjectNotFound
17
+ from prefect.utilities.importtools import lazy_import
18
+
19
+ if TYPE_CHECKING:
20
+ from prefect.client.orchestration import PrefectClient
21
+
22
+
23
+ coiled: ModuleType = lazy_import("coiled")
24
+
25
+
26
+ class CoiledPushProvisioner:
27
+ """
28
+ A infrastructure provisioner for Coiled push work pools.
29
+ """
30
+
31
+ def __init__(self, client: Optional["PrefectClient"] = None):
32
+ self._console = Console()
33
+
34
+ @property
35
+ def console(self) -> Console:
36
+ return self._console
37
+
38
+ @console.setter
39
+ def console(self, value: Console) -> None:
40
+ self._console = value
41
+
42
+ @staticmethod
43
+ def _is_coiled_installed() -> bool:
44
+ """
45
+ Checks if the coiled package is installed.
46
+
47
+ Returns:
48
+ True if the coiled package is installed, False otherwise
49
+ """
50
+ try:
51
+ importlib.import_module("coiled")
52
+ return True
53
+ except ModuleNotFoundError:
54
+ return False
55
+
56
+ async def _install_coiled(self):
57
+ """
58
+ Installs the coiled package.
59
+ """
60
+ with Progress(
61
+ SpinnerColumn(),
62
+ TextColumn("[bold blue]Installing coiled..."),
63
+ transient=True,
64
+ console=self.console,
65
+ ) as progress:
66
+ task = progress.add_task("coiled install")
67
+ progress.start()
68
+ global coiled
69
+ await run_process(
70
+ [shlex.quote(sys.executable), "-m", "pip", "install", "coiled"]
71
+ )
72
+ coiled = importlib.import_module("coiled")
73
+ progress.advance(task)
74
+
75
+ async def _get_coiled_token(self) -> str:
76
+ """
77
+ Gets a Coiled API token from the current Coiled configuration.
78
+ """
79
+ import dask.config
80
+
81
+ return dask.config.get("coiled.token", "")
82
+
83
+ async def _create_new_coiled_token(self):
84
+ """
85
+ Triggers a Coiled login via the browser if no current token. Will create a new token.
86
+ """
87
+ await run_process(["coiled", "login"])
88
+
89
+ async def _create_coiled_credentials_block(
90
+ self,
91
+ block_document_name: str,
92
+ coiled_token: str,
93
+ client: "PrefectClient",
94
+ ) -> BlockDocument:
95
+ """
96
+ Creates a CoiledCredentials block containing the provided token.
97
+
98
+ Args:
99
+ block_document_name: The name of the block document to create
100
+ coiled_token: The Coiled API token
101
+
102
+ Returns:
103
+ The ID of the created block
104
+ """
105
+ assert client is not None, "client injection failed"
106
+ try:
107
+ credentials_block_type = await client.read_block_type_by_slug(
108
+ "coiled-credentials"
109
+ )
110
+ except ObjectNotFound:
111
+ # Shouldn't happen, but just in case
112
+ raise RuntimeError(
113
+ "Unable to find CoiledCredentials block type. Please ensure you are"
114
+ " using Prefect Cloud."
115
+ )
116
+ credentials_block_schema = (
117
+ await client.get_most_recent_block_schema_for_block_type(
118
+ block_type_id=credentials_block_type.id
119
+ )
120
+ )
121
+ assert (
122
+ credentials_block_schema is not None
123
+ ), f"Unable to find schema for block type {credentials_block_type.slug}"
124
+
125
+ block_doc = await client.create_block_document(
126
+ block_document=BlockDocumentCreate(
127
+ name=block_document_name,
128
+ data={
129
+ "api_token": coiled_token,
130
+ },
131
+ block_type_id=credentials_block_type.id,
132
+ block_schema_id=credentials_block_schema.id,
133
+ )
134
+ )
135
+ return block_doc
136
+
137
+ @inject_client
138
+ async def provision(
139
+ self,
140
+ work_pool_name: str,
141
+ base_job_template: Dict[str, Any],
142
+ client: Optional["PrefectClient"] = None,
143
+ ) -> Dict[str, Any]:
144
+ """
145
+ Provisions resources necessary for a Coiled push work pool.
146
+
147
+ Provisioned resources:
148
+ - A CoiledCredentials block containing a Coiled API token
149
+
150
+ Args:
151
+ work_pool_name: The name of the work pool to provision resources for
152
+ base_job_template: The base job template to update
153
+
154
+ Returns:
155
+ A copy of the provided base job template with the provisioned resources
156
+ """
157
+ credentials_block_name = f"{work_pool_name}-coiled-credentials"
158
+ base_job_template_copy = deepcopy(base_job_template)
159
+ assert client is not None, "client injection failed"
160
+ try:
161
+ block_doc = await client.read_block_document_by_name(
162
+ credentials_block_name, "coiled-credentials"
163
+ )
164
+ self.console.print(
165
+ f"Work pool [blue]{work_pool_name!r}[/] will reuse the existing Coiled"
166
+ f" credentials block [blue]{credentials_block_name!r}[/blue]"
167
+ )
168
+ except ObjectNotFound:
169
+ if self._console.is_interactive and not Confirm.ask(
170
+ (
171
+ "\n"
172
+ "To configure your Coiled push work pool we'll need to store a Coiled"
173
+ " API token with Prefect Cloud as a block. We'll pull the token from"
174
+ " your local Coiled configuration or create a new token if we"
175
+ " can't find one.\n"
176
+ "\n"
177
+ "Would you like to continue?"
178
+ ),
179
+ console=self.console,
180
+ default=True,
181
+ ):
182
+ self.console.print(
183
+ "No problem! You can always configure your Coiled push work pool"
184
+ " later via the Prefect UI."
185
+ )
186
+ return base_job_template
187
+
188
+ if not self._is_coiled_installed():
189
+ if self.console.is_interactive and Confirm.ask(
190
+ (
191
+ "The [blue]coiled[/] package is required to configure"
192
+ " authentication for your work pool.\n"
193
+ "\n"
194
+ "Would you like to install it now?"
195
+ ),
196
+ console=self.console,
197
+ default=True,
198
+ ):
199
+ await self._install_coiled()
200
+
201
+ if not self._is_coiled_installed():
202
+ raise RuntimeError(
203
+ "The coiled package is not installed.\n\nPlease try installing coiled,"
204
+ " or you can use the Prefect UI to create your Coiled push work pool."
205
+ )
206
+
207
+ # Get the current Coiled API token
208
+ coiled_api_token = await self._get_coiled_token()
209
+ if not coiled_api_token:
210
+ # Create a new token one wasn't found
211
+ if self.console.is_interactive and Confirm.ask(
212
+ "Coiled credentials not found. Would you like to create a new token?",
213
+ console=self.console,
214
+ default=True,
215
+ ):
216
+ await self._create_new_coiled_token()
217
+ coiled_api_token = await self._get_coiled_token()
218
+ else:
219
+ raise RuntimeError(
220
+ "Coiled credentials not found. Please create a new token by"
221
+ " running [blue]coiled login[/] and try again."
222
+ )
223
+
224
+ # Create the credentials block
225
+ with Progress(
226
+ SpinnerColumn(),
227
+ TextColumn("[bold blue]Saving Coiled credentials..."),
228
+ transient=True,
229
+ console=self.console,
230
+ ) as progress:
231
+ task = progress.add_task("create coiled credentials block")
232
+ progress.start()
233
+ block_doc = await self._create_coiled_credentials_block(
234
+ credentials_block_name,
235
+ coiled_api_token,
236
+ client=client,
237
+ )
238
+ progress.advance(task)
239
+
240
+ base_job_template_copy["variables"]["properties"]["credentials"]["default"] = {
241
+ "$ref": {"block_document_id": str(block_doc.id)}
242
+ }
243
+ if "image" in base_job_template_copy["variables"]["properties"]:
244
+ base_job_template_copy["variables"]["properties"]["image"]["default"] = ""
245
+ self.console.print(
246
+ f"Successfully configured Coiled push work pool {work_pool_name!r}!",
247
+ style="green",
248
+ )
249
+ return base_job_template_copy
@@ -10,6 +10,7 @@ Classes:
10
10
  ContainerInstancePushProvisioner: A class for provisioning infrastructure using Azure Container Instances.
11
11
 
12
12
  """
13
+ from __future__ import annotations
13
14
 
14
15
  import json
15
16
  import random
@@ -64,7 +65,7 @@ class AzureCLI:
64
65
  failure_message: Optional[str] = None,
65
66
  ignore_if_exists: bool = False,
66
67
  return_json: bool = False,
67
- ):
68
+ ) -> str | dict[str, Any] | None:
68
69
  """
69
70
  Runs an Azure CLI command and processes the output.
70
71
 
@@ -156,7 +157,7 @@ class ContainerInstancePushProvisioner:
156
157
  self._subscription_name = None
157
158
  self._location = "eastus"
158
159
  self._identity_name = "prefect-acr-identity"
159
- self.azure_cli = AzureCLI(self.console)
160
+ self.azure_cli: AzureCLI = AzureCLI(self.console)
160
161
  self._credentials_block_name = None
161
162
  self._resource_group_name = "prefect-aci-push-pool-rg"
162
163
  self._app_registration_name = "prefect-aci-push-pool-app"
@@ -170,7 +171,7 @@ class ContainerInstancePushProvisioner:
170
171
  def console(self, value: Console) -> None:
171
172
  self._console = value
172
173
 
173
- async def set_location(self):
174
+ async def set_location(self) -> None:
174
175
  """
175
176
  Set the Azure resource deployment location to the default or 'eastus' on failure.
176
177
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import base64
2
4
  import contextlib
3
5
  import contextvars
@@ -9,9 +11,11 @@ import sys
9
11
  from copy import deepcopy
10
12
  from functools import partial
11
13
  from textwrap import dedent
12
- from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional
14
+ from types import ModuleType
15
+ from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, Optional
13
16
 
14
17
  import anyio
18
+ import anyio.to_thread
15
19
  from anyio import run_process
16
20
  from rich.console import Console
17
21
  from rich.panel import Panel
@@ -33,13 +37,15 @@ from prefect.utilities.importtools import lazy_import
33
37
  if TYPE_CHECKING:
34
38
  from prefect.client.orchestration import PrefectClient
35
39
 
36
- boto3 = lazy_import("boto3")
40
+ boto3: ModuleType = lazy_import("boto3")
37
41
 
38
- current_console = contextvars.ContextVar("console", default=Console())
42
+ current_console: contextvars.ContextVar[Console] = contextvars.ContextVar(
43
+ "console", default=Console()
44
+ )
39
45
 
40
46
 
41
47
  @contextlib.contextmanager
42
- def console_context(value: Console):
48
+ def console_context(value: Console) -> Generator[None, None, None]:
43
49
  token = current_console.set(value)
44
50
  try:
45
51
  yield
@@ -73,7 +79,7 @@ class IamPolicyResource:
73
79
  """
74
80
  return 1 if await self.requires_provisioning() else 0
75
81
 
76
- def _get_policy_by_name(self, name):
82
+ def _get_policy_by_name(self, name: str) -> dict[str, Any] | None:
77
83
  paginator = self._iam_client.get_paginator("list_policies")
78
84
  page_iterator = paginator.paginate(Scope="Local")
79
85
 
@@ -119,9 +125,9 @@ class IamPolicyResource:
119
125
 
120
126
  async def provision(
121
127
  self,
122
- policy_document: Dict[str, Any],
128
+ policy_document: dict[str, Any],
123
129
  advance: Callable[[], None],
124
- ):
130
+ ) -> str:
125
131
  """
126
132
  Provisions an IAM policy.
127
133
 
@@ -153,7 +159,7 @@ class IamPolicyResource:
153
159
  return policy["Arn"]
154
160
 
155
161
  @property
156
- def next_steps(self):
162
+ def next_steps(self) -> list[str]:
157
163
  return []
158
164
 
159
165
 
@@ -215,7 +221,7 @@ class IamUserResource:
215
221
  async def provision(
216
222
  self,
217
223
  advance: Callable[[], None],
218
- ):
224
+ ) -> None:
219
225
  """
220
226
  Provisions an IAM user.
221
227
 
@@ -231,7 +237,7 @@ class IamUserResource:
231
237
  advance()
232
238
 
233
239
  @property
234
- def next_steps(self):
240
+ def next_steps(self) -> list[str]:
235
241
  return []
236
242
 
237
243
 
@@ -241,7 +247,7 @@ class CredentialsBlockResource:
241
247
  self._user_name = user_name
242
248
  self._requires_provisioning = None
243
249
 
244
- async def get_task_count(self):
250
+ async def get_task_count(self) -> int:
245
251
  """
246
252
  Returns the number of tasks that will be executed to provision this resource.
247
253
 
@@ -357,7 +363,7 @@ class CredentialsBlockResource:
357
363
  }
358
364
 
359
365
  @property
360
- def next_steps(self):
366
+ def next_steps(self) -> list[str]:
361
367
  return []
362
368
 
363
369
 
@@ -374,7 +380,7 @@ class AuthenticationResource:
374
380
  credentials_block_name or f"{work_pool_name}-aws-credentials"
375
381
  )
376
382
  self._policy_name = policy_name
377
- self._policy_document = {
383
+ self._policy_document: dict[str, Any] = {
378
384
  "Version": "2012-10-17",
379
385
  "Statement": [
380
386
  {
@@ -417,7 +423,11 @@ class AuthenticationResource:
417
423
  self._execution_role_resource = ExecutionRoleResource()
418
424
 
419
425
  @property
420
- def resources(self):
426
+ def resources(
427
+ self,
428
+ ) -> list[
429
+ "ExecutionRoleResource | IamUserResource | IamPolicyResource | CredentialsBlockResource"
430
+ ]:
421
431
  return [
422
432
  self._execution_role_resource,
423
433
  self._iam_user_resource,
@@ -425,7 +435,7 @@ class AuthenticationResource:
425
435
  self._credentials_block_resource,
426
436
  ]
427
437
 
428
- async def get_task_count(self):
438
+ async def get_task_count(self) -> int:
429
439
  """
430
440
  Returns the number of tasks that will be executed to provision this resource.
431
441
 
@@ -461,9 +471,9 @@ class AuthenticationResource:
461
471
 
462
472
  async def provision(
463
473
  self,
464
- base_job_template: Dict[str, Any],
474
+ base_job_template: dict[str, Any],
465
475
  advance: Callable[[], None],
466
- ):
476
+ ) -> None:
467
477
  """
468
478
  Provisions the authentication resources.
469
479
 
@@ -507,7 +517,7 @@ class AuthenticationResource:
507
517
  )
508
518
 
509
519
  @property
510
- def next_steps(self):
520
+ def next_steps(self) -> list[str]:
511
521
  return [
512
522
  next_step
513
523
  for resource in self.resources
@@ -521,7 +531,7 @@ class ClusterResource:
521
531
  self._cluster_name = cluster_name
522
532
  self._requires_provisioning = None
523
533
 
524
- async def get_task_count(self):
534
+ async def get_task_count(self) -> int:
525
535
  """
526
536
  Returns the number of tasks that will be executed to provision this resource.
527
537
 
@@ -566,9 +576,9 @@ class ClusterResource:
566
576
 
567
577
  async def provision(
568
578
  self,
569
- base_job_template: Dict[str, Any],
579
+ base_job_template: dict[str, Any],
570
580
  advance: Callable[[], None],
571
- ):
581
+ ) -> None:
572
582
  """
573
583
  Provisions an ECS cluster.
574
584
 
@@ -592,7 +602,7 @@ class ClusterResource:
592
602
  ] = self._cluster_name
593
603
 
594
604
  @property
595
- def next_steps(self):
605
+ def next_steps(self) -> list[str]:
596
606
  return []
597
607
 
598
608
 
@@ -608,7 +618,7 @@ class VpcResource:
608
618
  self._requires_provisioning = None
609
619
  self._ecs_security_group_name = ecs_security_group_name
610
620
 
611
- async def get_task_count(self):
621
+ async def get_task_count(self) -> int:
612
622
  """
613
623
  Returns the number of tasks that will be executed to provision this resource.
614
624
 
@@ -642,7 +652,9 @@ class VpcResource:
642
652
  response = await anyio.to_thread.run_sync(self._ec2_client.describe_vpcs)
643
653
  return [vpc["CidrBlock"] for vpc in response["Vpcs"]]
644
654
 
645
- async def _find_non_overlapping_cidr(self, default_cidr="172.31.0.0/16"):
655
+ async def _find_non_overlapping_cidr(
656
+ self, default_cidr: str = "172.31.0.0/16"
657
+ ) -> str:
646
658
  """Find a non-overlapping CIDR block"""
647
659
  response = await anyio.to_thread.run_sync(self._ec2_client.describe_vpcs)
648
660
  existing_cidrs = [vpc["CidrBlock"] for vpc in response["Vpcs"]]
@@ -708,9 +720,9 @@ class VpcResource:
708
720
 
709
721
  async def provision(
710
722
  self,
711
- base_job_template: Dict[str, Any],
723
+ base_job_template: dict[str, Any],
712
724
  advance: Callable[[], None],
713
- ):
725
+ ) -> None:
714
726
  """
715
727
  Provisions a VPC.
716
728
 
@@ -768,7 +780,7 @@ class VpcResource:
768
780
  )
769
781
  )["AvailabilityZones"]
770
782
  zones = [az["ZoneName"] for az in azs]
771
- subnets = []
783
+ subnets: list[Any] = []
772
784
  for i, subnet_cidr in enumerate(subnet_cidrs[0:3]):
773
785
  subnets.append(
774
786
  await anyio.to_thread.run_sync(
@@ -828,7 +840,7 @@ class VpcResource:
828
840
  )
829
841
 
830
842
  @property
831
- def next_steps(self):
843
+ def next_steps(self) -> list[str]:
832
844
  return []
833
845
 
834
846
 
@@ -838,9 +850,9 @@ class ContainerRepositoryResource:
838
850
  self._repository_name = repository_name
839
851
  self._requires_provisioning = None
840
852
  self._work_pool_name = work_pool_name
841
- self._next_steps = []
853
+ self._next_steps: list[str | Panel] = []
842
854
 
843
- async def get_task_count(self):
855
+ async def get_task_count(self) -> int:
844
856
  """
845
857
  Returns the number of tasks that will be executed to provision this resource.
846
858
 
@@ -895,9 +907,9 @@ class ContainerRepositoryResource:
895
907
 
896
908
  async def provision(
897
909
  self,
898
- base_job_template: Dict[str, Any],
910
+ base_job_template: dict[str, Any],
899
911
  advance: Callable[[], None],
900
- ):
912
+ ) -> None:
901
913
  """
902
914
  Provisions an ECR repository.
903
915
 
@@ -978,7 +990,7 @@ class ContainerRepositoryResource:
978
990
  )
979
991
 
980
992
  @property
981
- def next_steps(self):
993
+ def next_steps(self) -> list[str | Panel]:
982
994
  return self._next_steps
983
995
 
984
996
 
@@ -1000,7 +1012,7 @@ class ExecutionRoleResource:
1000
1012
  )
1001
1013
  self._requires_provisioning = None
1002
1014
 
1003
- async def get_task_count(self):
1015
+ async def get_task_count(self) -> int:
1004
1016
  """
1005
1017
  Returns the number of tasks that will be executed to provision this resource.
1006
1018
 
@@ -1046,9 +1058,9 @@ class ExecutionRoleResource:
1046
1058
 
1047
1059
  async def provision(
1048
1060
  self,
1049
- base_job_template: Dict[str, Any],
1061
+ base_job_template: dict[str, Any],
1050
1062
  advance: Callable[[], None],
1051
- ):
1063
+ ) -> str:
1052
1064
  """
1053
1065
  Provisions an IAM role.
1054
1066
 
@@ -1087,7 +1099,7 @@ class ExecutionRoleResource:
1087
1099
  return response["Role"]["Arn"]
1088
1100
 
1089
1101
  @property
1090
- def next_steps(self):
1102
+ def next_steps(self) -> list[str]:
1091
1103
  return []
1092
1104
 
1093
1105
 
@@ -1100,11 +1112,11 @@ class ElasticContainerServicePushProvisioner:
1100
1112
  self._console = Console()
1101
1113
 
1102
1114
  @property
1103
- def console(self):
1115
+ def console(self) -> Console:
1104
1116
  return self._console
1105
1117
 
1106
1118
  @console.setter
1107
- def console(self, value):
1119
+ def console(self, value: Console) -> None:
1108
1120
  self._console = value
1109
1121
 
1110
1122
  async def _prompt_boto3_installation(self):
@@ -1115,7 +1127,7 @@ class ElasticContainerServicePushProvisioner:
1115
1127
  boto3 = importlib.import_module("boto3")
1116
1128
 
1117
1129
  @staticmethod
1118
- def is_boto3_installed():
1130
+ def is_boto3_installed() -> bool:
1119
1131
  """
1120
1132
  Check if boto3 is installed.
1121
1133
  """
@@ -1157,8 +1169,8 @@ class ElasticContainerServicePushProvisioner:
1157
1169
  async def provision(
1158
1170
  self,
1159
1171
  work_pool_name: str,
1160
- base_job_template: dict,
1161
- ) -> Dict[str, Any]:
1172
+ base_job_template: dict[str, Any],
1173
+ ) -> dict[str, Any]:
1162
1174
  """
1163
1175
  Provisions the infrastructure for an ECS push work pool.
1164
1176
 
@@ -1310,7 +1322,7 @@ class ElasticContainerServicePushProvisioner:
1310
1322
  # provision calls will be no-ops, but update the base job template
1311
1323
 
1312
1324
  base_job_template_copy = deepcopy(base_job_template)
1313
- next_steps = []
1325
+ next_steps: list[str | Panel] = []
1314
1326
  with Progress(console=self._console, disable=num_tasks == 0) as progress:
1315
1327
  task = progress.add_task(
1316
1328
  "Provisioning Infrastructure",
@@ -2,6 +2,7 @@ import importlib
2
2
  import shlex
3
3
  import sys
4
4
  from copy import deepcopy
5
+ from types import ModuleType
5
6
  from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple
6
7
 
7
8
  from anyio import run_process
@@ -19,7 +20,7 @@ if TYPE_CHECKING:
19
20
  from prefect.client.orchestration import PrefectClient
20
21
 
21
22
 
22
- modal = lazy_import("modal")
23
+ modal: ModuleType = lazy_import("modal")
23
24
 
24
25
 
25
26
  class ModalPushProvisioner:
@@ -28,14 +29,14 @@ class ModalPushProvisioner:
28
29
  """
29
30
 
30
31
  def __init__(self, client: Optional["PrefectClient"] = None):
31
- self._console = Console()
32
+ self._console: Console = Console()
32
33
 
33
34
  @property
34
- def console(self):
35
+ def console(self) -> Console:
35
36
  return self._console
36
37
 
37
38
  @console.setter
38
- def console(self, value):
39
+ def console(self, value: Console) -> None:
39
40
  self._console = value
40
41
 
41
42
  @staticmethod
prefect/input/actions.py CHANGED
@@ -1,3 +1,4 @@
1
+ import inspect
1
2
  from typing import TYPE_CHECKING, Any, Optional, Set
2
3
  from uuid import UUID
3
4
 
@@ -44,9 +45,12 @@ async def create_flow_run_input_from_model(
44
45
  else:
45
46
  json_safe = orjson.loads(model_instance.json())
46
47
 
47
- await create_flow_run_input(
48
+ coro = create_flow_run_input(
48
49
  key=key, value=json_safe, flow_run_id=flow_run_id, sender=sender
49
50
  )
51
+ if TYPE_CHECKING:
52
+ assert inspect.iscoroutine(coro)
53
+ await coro
50
54
 
51
55
 
52
56
  @sync_compatible