prefect-client 3.1.12__py3-none-any.whl → 3.1.14__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 (111) hide show
  1. prefect/_experimental/lineage.py +63 -0
  2. prefect/_experimental/sla/client.py +53 -27
  3. prefect/_experimental/sla/objects.py +10 -2
  4. prefect/_internal/concurrency/services.py +2 -2
  5. prefect/_internal/concurrency/threads.py +6 -0
  6. prefect/_internal/retries.py +6 -3
  7. prefect/_internal/schemas/validators.py +6 -4
  8. prefect/_version.py +3 -3
  9. prefect/artifacts.py +4 -1
  10. prefect/automations.py +1 -1
  11. prefect/blocks/abstract.py +5 -2
  12. prefect/blocks/notifications.py +1 -0
  13. prefect/cache_policies.py +70 -22
  14. prefect/client/orchestration/_automations/client.py +4 -0
  15. prefect/client/orchestration/_deployments/client.py +3 -3
  16. prefect/client/utilities.py +3 -3
  17. prefect/context.py +16 -6
  18. prefect/deployments/base.py +7 -4
  19. prefect/deployments/flow_runs.py +5 -1
  20. prefect/deployments/runner.py +6 -11
  21. prefect/deployments/steps/core.py +1 -1
  22. prefect/deployments/steps/pull.py +8 -3
  23. prefect/deployments/steps/utility.py +2 -2
  24. prefect/docker/docker_image.py +13 -9
  25. prefect/engine.py +19 -10
  26. prefect/events/cli/automations.py +4 -4
  27. prefect/events/clients.py +17 -14
  28. prefect/events/filters.py +34 -34
  29. prefect/events/schemas/automations.py +12 -8
  30. prefect/events/schemas/events.py +5 -1
  31. prefect/events/worker.py +1 -1
  32. prefect/filesystems.py +1 -1
  33. prefect/flow_engine.py +172 -123
  34. prefect/flows.py +119 -74
  35. prefect/futures.py +14 -7
  36. prefect/infrastructure/provisioners/__init__.py +2 -0
  37. prefect/infrastructure/provisioners/cloud_run.py +4 -4
  38. prefect/infrastructure/provisioners/coiled.py +249 -0
  39. prefect/infrastructure/provisioners/container_instance.py +4 -3
  40. prefect/infrastructure/provisioners/ecs.py +55 -43
  41. prefect/infrastructure/provisioners/modal.py +5 -4
  42. prefect/input/actions.py +5 -1
  43. prefect/input/run_input.py +157 -43
  44. prefect/logging/configuration.py +5 -8
  45. prefect/logging/filters.py +2 -2
  46. prefect/logging/formatters.py +15 -11
  47. prefect/logging/handlers.py +24 -14
  48. prefect/logging/highlighters.py +5 -5
  49. prefect/logging/loggers.py +29 -20
  50. prefect/main.py +3 -1
  51. prefect/results.py +166 -86
  52. prefect/runner/runner.py +112 -84
  53. prefect/runner/server.py +3 -1
  54. prefect/runner/storage.py +18 -18
  55. prefect/runner/submit.py +19 -12
  56. prefect/runtime/deployment.py +15 -8
  57. prefect/runtime/flow_run.py +19 -6
  58. prefect/runtime/task_run.py +7 -3
  59. prefect/settings/base.py +17 -7
  60. prefect/settings/legacy.py +4 -4
  61. prefect/settings/models/api.py +4 -3
  62. prefect/settings/models/cli.py +4 -3
  63. prefect/settings/models/client.py +7 -4
  64. prefect/settings/models/cloud.py +4 -3
  65. prefect/settings/models/deployments.py +4 -3
  66. prefect/settings/models/experiments.py +4 -3
  67. prefect/settings/models/flows.py +4 -3
  68. prefect/settings/models/internal.py +4 -3
  69. prefect/settings/models/logging.py +8 -6
  70. prefect/settings/models/results.py +4 -3
  71. prefect/settings/models/root.py +11 -16
  72. prefect/settings/models/runner.py +8 -5
  73. prefect/settings/models/server/api.py +6 -3
  74. prefect/settings/models/server/database.py +120 -25
  75. prefect/settings/models/server/deployments.py +4 -3
  76. prefect/settings/models/server/ephemeral.py +7 -4
  77. prefect/settings/models/server/events.py +6 -3
  78. prefect/settings/models/server/flow_run_graph.py +4 -3
  79. prefect/settings/models/server/root.py +4 -3
  80. prefect/settings/models/server/services.py +15 -12
  81. prefect/settings/models/server/tasks.py +7 -4
  82. prefect/settings/models/server/ui.py +4 -3
  83. prefect/settings/models/tasks.py +10 -5
  84. prefect/settings/models/testing.py +4 -3
  85. prefect/settings/models/worker.py +7 -4
  86. prefect/settings/profiles.py +13 -12
  87. prefect/settings/sources.py +20 -19
  88. prefect/states.py +17 -13
  89. prefect/task_engine.py +43 -33
  90. prefect/task_runners.py +35 -23
  91. prefect/task_runs.py +20 -11
  92. prefect/task_worker.py +12 -7
  93. prefect/tasks.py +67 -25
  94. prefect/telemetry/bootstrap.py +4 -1
  95. prefect/telemetry/run_telemetry.py +15 -13
  96. prefect/transactions.py +3 -3
  97. prefect/types/__init__.py +9 -6
  98. prefect/types/_datetime.py +19 -0
  99. prefect/utilities/_deprecated.py +38 -0
  100. prefect/utilities/engine.py +11 -4
  101. prefect/utilities/filesystem.py +2 -2
  102. prefect/utilities/generics.py +1 -1
  103. prefect/utilities/pydantic.py +21 -36
  104. prefect/workers/base.py +52 -30
  105. prefect/workers/process.py +20 -15
  106. prefect/workers/server.py +4 -5
  107. {prefect_client-3.1.12.dist-info → prefect_client-3.1.14.dist-info}/METADATA +2 -2
  108. {prefect_client-3.1.12.dist-info → prefect_client-3.1.14.dist-info}/RECORD +111 -108
  109. {prefect_client-3.1.12.dist-info → prefect_client-3.1.14.dist-info}/LICENSE +0 -0
  110. {prefect_client-3.1.12.dist-info → prefect_client-3.1.14.dist-info}/WHEEL +0 -0
  111. {prefect_client-3.1.12.dist-info → prefect_client-3.1.14.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ import pendulum
4
+ from pendulum.date import Date as PendulumDate
5
+ from pendulum.datetime import DateTime as PendulumDateTime
6
+ from pendulum.duration import Duration as PendulumDuration
7
+ from pendulum.time import Time as PendulumTime
8
+ from pydantic_extra_types.pendulum_dt import Date as PydanticDate
9
+ from pydantic_extra_types.pendulum_dt import DateTime as PydanticDateTime
10
+ from typing_extensions import TypeAlias
11
+
12
+ DateTime: TypeAlias = PydanticDateTime
13
+ Date: TypeAlias = PydanticDate
14
+
15
+
16
+ def parse_datetime(
17
+ value: str,
18
+ ) -> PendulumDateTime | PendulumDate | PendulumTime | PendulumDuration:
19
+ return pendulum.parse(value)
@@ -0,0 +1,38 @@
1
+ from typing import Any
2
+
3
+ from jsonpatch import ( # type: ignore # no typing stubs available, see https://github.com/stefankoegl/python-json-patch/issues/158
4
+ JsonPatch as JsonPatchBase,
5
+ )
6
+ from pydantic import GetJsonSchemaHandler
7
+ from pydantic.json_schema import JsonSchemaValue
8
+ from pydantic_core import core_schema
9
+
10
+
11
+ class JsonPatch(JsonPatchBase):
12
+ @classmethod
13
+ def __get_pydantic_core_schema__(
14
+ cls, source_type: Any, handler: GetJsonSchemaHandler
15
+ ) -> core_schema.CoreSchema:
16
+ return core_schema.typed_dict_schema(
17
+ {"patch": core_schema.typed_dict_field(core_schema.dict_schema())}
18
+ )
19
+
20
+ @classmethod
21
+ def __get_pydantic_json_schema__(
22
+ cls, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
23
+ ) -> JsonSchemaValue:
24
+ json_schema = handler(core_schema)
25
+ json_schema = handler.resolve_ref_schema(json_schema)
26
+ json_schema.pop("required", None)
27
+ json_schema.pop("properties", None)
28
+ json_schema.update(
29
+ {
30
+ "type": "array",
31
+ "format": "rfc6902",
32
+ "items": {
33
+ "type": "object",
34
+ "additionalProperties": {"type": "string"},
35
+ },
36
+ }
37
+ )
38
+ return json_schema
@@ -757,12 +757,19 @@ def resolve_to_final_result(expr: Any, context: dict[str, Any]) -> Any:
757
757
  parameter_context = propagate.extract(
758
758
  {"traceparent": state.state_details.traceparent}
759
759
  )
760
- trace.get_current_span().add_link(
761
- context=trace.get_current_span(parameter_context).get_span_context(),
762
- attributes={
760
+ attributes = {}
761
+
762
+ # If this future is being used as a parameter (as opposed to just a wait_for),
763
+ # add attributes to the span to indicate the parameter name and type
764
+ if "parameter_name" in context:
765
+ attributes = {
763
766
  "prefect.input.name": context["parameter_name"],
764
767
  "prefect.input.type": type(result).__name__,
765
- },
768
+ }
769
+
770
+ trace.get_current_span().add_link(
771
+ context=trace.get_current_span(parameter_context).get_span_context(),
772
+ attributes=attributes,
766
773
  )
767
774
 
768
775
  return result
@@ -8,7 +8,7 @@ import threading
8
8
  from collections.abc import Iterable
9
9
  from contextlib import contextmanager
10
10
  from pathlib import Path, PureWindowsPath
11
- from typing import AnyStr, Optional, Union, cast
11
+ from typing import Any, AnyStr, Optional, Union, cast
12
12
 
13
13
  # fsspec has no stubs, see https://github.com/fsspec/filesystem_spec/issues/625
14
14
  import fsspec # type: ignore
@@ -114,7 +114,7 @@ def filename(path: str) -> str:
114
114
  return path.split(sep)[-1]
115
115
 
116
116
 
117
- def is_local_path(path: Union[str, pathlib.Path, OpenFile]) -> bool:
117
+ def is_local_path(path: Union[str, pathlib.Path, Any]) -> bool:
118
118
  """Check if the given path points to a local or remote file system"""
119
119
  if isinstance(path, str):
120
120
  try:
@@ -5,7 +5,7 @@ from pydantic_core import SchemaValidator, core_schema
5
5
 
6
6
  T = TypeVar("T", bound=BaseModel)
7
7
 
8
- ListValidator = SchemaValidator(
8
+ ListValidator: SchemaValidator = SchemaValidator(
9
9
  schema=core_schema.list_schema(
10
10
  items_schema=core_schema.dict_schema(
11
11
  keys_schema=core_schema.str_schema(), values_schema=core_schema.any_schema()
@@ -1,3 +1,4 @@
1
+ import warnings
1
2
  from typing import (
2
3
  Any,
3
4
  Callable,
@@ -10,18 +11,13 @@ from typing import (
10
11
  overload,
11
12
  )
12
13
 
13
- from jsonpatch import ( # type: ignore # no typing stubs available, see https://github.com/stefankoegl/python-json-patch/issues/158
14
- JsonPatch as JsonPatchBase,
15
- )
16
14
  from pydantic import (
17
15
  BaseModel,
18
- GetJsonSchemaHandler,
19
16
  Secret,
20
17
  TypeAdapter,
21
18
  ValidationError,
22
19
  )
23
- from pydantic.json_schema import JsonSchemaValue
24
- from pydantic_core import core_schema, to_jsonable_python
20
+ from pydantic_core import to_jsonable_python
25
21
  from typing_extensions import Literal
26
22
 
27
23
  from prefect.utilities.dispatch import get_dispatch_key, lookup_type, register_base_type
@@ -262,36 +258,6 @@ class PartialModel(Generic[M]):
262
258
  return f"PartialModel(cls={self.model_cls.__name__}, {dsp_fields})"
263
259
 
264
260
 
265
- class JsonPatch(JsonPatchBase):
266
- @classmethod
267
- def __get_pydantic_core_schema__(
268
- cls, source_type: Any, handler: GetJsonSchemaHandler
269
- ) -> core_schema.CoreSchema:
270
- return core_schema.typed_dict_schema(
271
- {"patch": core_schema.typed_dict_field(core_schema.dict_schema())}
272
- )
273
-
274
- @classmethod
275
- def __get_pydantic_json_schema__(
276
- cls, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
277
- ) -> JsonSchemaValue:
278
- json_schema = handler(core_schema)
279
- json_schema = handler.resolve_ref_schema(json_schema)
280
- json_schema.pop("required", None)
281
- json_schema.pop("properties", None)
282
- json_schema.update(
283
- {
284
- "type": "array",
285
- "format": "rfc6902",
286
- "items": {
287
- "type": "object",
288
- "additionalProperties": {"type": "string"},
289
- },
290
- }
291
- )
292
- return json_schema
293
-
294
-
295
261
  def custom_pydantic_encoder(
296
262
  type_encoders: dict[Any, Callable[[type[Any]], Any]], obj: Any
297
263
  ) -> Any:
@@ -382,3 +348,22 @@ def handle_secret_render(value: object, context: dict[str, Any]) -> object:
382
348
  elif isinstance(value, BaseModel):
383
349
  return value.model_dump(context=context)
384
350
  return value
351
+
352
+
353
+ def __getattr__(name: str) -> Any:
354
+ """
355
+ Handles imports from this module that are deprecated.
356
+ """
357
+
358
+ if name == "JsonPatch":
359
+ warnings.warn(
360
+ "JsonPatch is deprecated and will be removed after March 2025. "
361
+ "Please use `JsonPatch` from the `jsonpatch` package instead.",
362
+ DeprecationWarning,
363
+ stacklevel=2,
364
+ )
365
+ from ._deprecated import JsonPatch
366
+
367
+ return JsonPatch
368
+ else:
369
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
prefect/workers/base.py CHANGED
@@ -1,9 +1,22 @@
1
+ from __future__ import annotations
2
+
1
3
  import abc
2
4
  import asyncio
3
5
  import threading
4
6
  from contextlib import AsyncExitStack
5
7
  from functools import partial
6
- from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set, Type, Union
8
+ from typing import (
9
+ TYPE_CHECKING,
10
+ Any,
11
+ Callable,
12
+ Dict,
13
+ Generic,
14
+ List,
15
+ Optional,
16
+ Set,
17
+ Type,
18
+ Union,
19
+ )
7
20
  from uuid import UUID, uuid4
8
21
 
9
22
  import anyio
@@ -13,7 +26,7 @@ import pendulum
13
26
  from importlib_metadata import distributions
14
27
  from pydantic import BaseModel, Field, PrivateAttr, field_validator
15
28
  from pydantic.json_schema import GenerateJsonSchema
16
- from typing_extensions import Literal
29
+ from typing_extensions import Literal, Self, TypeVar
17
30
 
18
31
  import prefect
19
32
  from prefect._internal.schemas.validators import return_v_or_none
@@ -104,7 +117,7 @@ class BaseJobConfiguration(BaseModel):
104
117
  _related_objects: Dict[str, Any] = PrivateAttr(default_factory=dict)
105
118
 
106
119
  @property
107
- def is_using_a_runner(self):
120
+ def is_using_a_runner(self) -> bool:
108
121
  return self.command is not None and "prefect flow-run execute" in self.command
109
122
 
110
123
  @field_validator("command")
@@ -175,7 +188,7 @@ class BaseJobConfiguration(BaseModel):
175
188
  return cls(**populated_configuration)
176
189
 
177
190
  @classmethod
178
- def json_template(cls) -> dict:
191
+ def json_template(cls) -> dict[str, Any]:
179
192
  """Returns a dict with job configuration as keys and the corresponding templates as values
180
193
 
181
194
  Defaults to using the job configuration parameter name as the template variable name.
@@ -186,7 +199,7 @@ class BaseJobConfiguration(BaseModel):
186
199
  key2: '{{ template2 }}', # `template2` specifically provide as template
187
200
  }
188
201
  """
189
- configuration = {}
202
+ configuration: dict[str, Any] = {}
190
203
  properties = cls.model_json_schema()["properties"]
191
204
  for k, v in properties.items():
192
205
  if v.get("template"):
@@ -202,7 +215,7 @@ class BaseJobConfiguration(BaseModel):
202
215
  flow_run: "FlowRun",
203
216
  deployment: Optional["DeploymentResponse"] = None,
204
217
  flow: Optional["Flow"] = None,
205
- ):
218
+ ) -> None:
206
219
  """
207
220
  Prepare the job configuration for a flow run.
208
221
 
@@ -368,15 +381,20 @@ class BaseWorkerResult(BaseModel, abc.ABC):
368
381
  identifier: str
369
382
  status_code: int
370
383
 
371
- def __bool__(self):
384
+ def __bool__(self) -> bool:
372
385
  return self.status_code == 0
373
386
 
374
387
 
388
+ C = TypeVar("C", bound=BaseJobConfiguration)
389
+ V = TypeVar("V", bound=BaseVariables)
390
+ R = TypeVar("R", bound=BaseWorkerResult)
391
+
392
+
375
393
  @register_base_type
376
- class BaseWorker(abc.ABC):
394
+ class BaseWorker(abc.ABC, Generic[C, V, R]):
377
395
  type: str
378
- job_configuration: Type[BaseJobConfiguration] = BaseJobConfiguration
379
- job_configuration_variables: Optional[Type[BaseVariables]] = None
396
+ job_configuration: Type[C] = BaseJobConfiguration # type: ignore
397
+ job_configuration_variables: Optional[Type[V]] = None
380
398
 
381
399
  _documentation_url = ""
382
400
  _logo_url = ""
@@ -418,7 +436,7 @@ class BaseWorker(abc.ABC):
418
436
  """
419
437
  if name and ("/" in name or "%" in name):
420
438
  raise ValueError("Worker name cannot contain '/' or '%'")
421
- self.name = name or f"{self.__class__.__name__} {uuid4()}"
439
+ self.name: str = name or f"{self.__class__.__name__} {uuid4()}"
422
440
  self._started_event: Optional[Event] = None
423
441
  self.backend_id: Optional[UUID] = None
424
442
  self._logger = get_worker_logger(self)
@@ -432,7 +450,7 @@ class BaseWorker(abc.ABC):
432
450
  self._prefetch_seconds: float = (
433
451
  prefetch_seconds or PREFECT_WORKER_PREFETCH_SECONDS.value()
434
452
  )
435
- self.heartbeat_interval_seconds = (
453
+ self.heartbeat_interval_seconds: int = (
436
454
  heartbeat_interval_seconds or PREFECT_WORKER_HEARTBEAT_SECONDS.value()
437
455
  )
438
456
 
@@ -461,7 +479,7 @@ class BaseWorker(abc.ABC):
461
479
  return cls._description
462
480
 
463
481
  @classmethod
464
- def get_default_base_job_template(cls) -> Dict:
482
+ def get_default_base_job_template(cls) -> dict[str, Any]:
465
483
  if cls.job_configuration_variables is None:
466
484
  schema = cls.job_configuration.model_json_schema()
467
485
  # remove "template" key from all dicts in schema['properties'] because it is not a
@@ -479,7 +497,9 @@ class BaseWorker(abc.ABC):
479
497
  }
480
498
 
481
499
  @staticmethod
482
- def get_worker_class_from_type(type: str) -> Optional[Type["BaseWorker"]]:
500
+ def get_worker_class_from_type(
501
+ type: str,
502
+ ) -> Optional[Type["BaseWorker[Any, Any, Any]"]]:
483
503
  """
484
504
  Returns the worker class for a given worker type. If the worker type
485
505
  is not recognized, returns None.
@@ -500,7 +520,7 @@ class BaseWorker(abc.ABC):
500
520
  return list(worker_registry.keys())
501
521
  return []
502
522
 
503
- def get_name_slug(self):
523
+ def get_name_slug(self) -> str:
504
524
  return slugify(self.name)
505
525
 
506
526
  def get_flow_run_logger(self, flow_run: "FlowRun") -> PrefectLogAdapter:
@@ -524,7 +544,7 @@ class BaseWorker(abc.ABC):
524
544
  run_once: bool = False,
525
545
  with_healthcheck: bool = False,
526
546
  printer: Callable[..., None] = print,
527
- ):
547
+ ) -> None:
528
548
  """
529
549
  Starts the worker and runs the main worker loops.
530
550
 
@@ -603,9 +623,9 @@ class BaseWorker(abc.ABC):
603
623
  async def run(
604
624
  self,
605
625
  flow_run: "FlowRun",
606
- configuration: BaseJobConfiguration,
607
- task_status: Optional[anyio.abc.TaskStatus] = None,
608
- ) -> BaseWorkerResult:
626
+ configuration: C,
627
+ task_status: Optional[anyio.abc.TaskStatus[int]] = None,
628
+ ) -> R:
609
629
  """
610
630
  Runs a given flow run on the current worker.
611
631
  """
@@ -614,12 +634,12 @@ class BaseWorker(abc.ABC):
614
634
  )
615
635
 
616
636
  @classmethod
617
- def __dispatch_key__(cls):
637
+ def __dispatch_key__(cls) -> str | None:
618
638
  if cls.__name__ == "BaseWorker":
619
639
  return None # The base class is abstract
620
640
  return cls.type
621
641
 
622
- async def setup(self):
642
+ async def setup(self) -> None:
623
643
  """Prepares the worker to run."""
624
644
  self._logger.debug("Setting up worker...")
625
645
  self._runs_task_group = anyio.create_task_group()
@@ -637,10 +657,10 @@ class BaseWorker(abc.ABC):
637
657
 
638
658
  self.is_setup = True
639
659
 
640
- async def teardown(self, *exc_info):
660
+ async def teardown(self, *exc_info: Any) -> None:
641
661
  """Cleans up resources after the worker is stopped."""
642
662
  self._logger.debug("Tearing down worker...")
643
- self.is_setup = False
663
+ self.is_setup: bool = False
644
664
  for scope in self._scheduled_task_scopes:
645
665
  scope.cancel()
646
666
 
@@ -684,14 +704,16 @@ class BaseWorker(abc.ABC):
684
704
 
685
705
  return is_still_polling
686
706
 
687
- async def get_and_submit_flow_runs(self):
707
+ async def get_and_submit_flow_runs(self) -> list["FlowRun"]:
688
708
  runs_response = await self._get_scheduled_flow_runs()
689
709
 
690
710
  self._last_polled_time = pendulum.now("utc")
691
711
 
692
712
  return await self._submit_scheduled_flow_runs(flow_run_response=runs_response)
693
713
 
694
- async def _update_local_work_pool_info(self):
714
+ async def _update_local_work_pool_info(self) -> None:
715
+ if TYPE_CHECKING:
716
+ assert self._client is not None
695
717
  try:
696
718
  work_pool = await self._client.read_work_pool(
697
719
  work_pool_name=self._work_pool_name
@@ -803,7 +825,7 @@ class BaseWorker(abc.ABC):
803
825
 
804
826
  return worker_id
805
827
 
806
- async def sync_with_backend(self):
828
+ async def sync_with_backend(self) -> None:
807
829
  """
808
830
  Updates the worker's local information about it's current work pool and
809
831
  queues. Sends a worker heartbeat to the API.
@@ -1042,7 +1064,7 @@ class BaseWorker(abc.ABC):
1042
1064
  self._limiter.release_on_behalf_of(flow_run_id)
1043
1065
  self._logger.debug("Limit slot released for flow run '%s'", flow_run_id)
1044
1066
 
1045
- def get_status(self):
1067
+ def get_status(self) -> dict[str, Any]:
1046
1068
  """
1047
1069
  Retrieves the status of the current worker including its name, current worker
1048
1070
  pool, the work pool queues it is polling, and its local settings.
@@ -1234,17 +1256,17 @@ class BaseWorker(abc.ABC):
1234
1256
 
1235
1257
  await self._client.update_flow_run_labels(flow_run_id, labels)
1236
1258
 
1237
- async def __aenter__(self):
1259
+ async def __aenter__(self) -> Self:
1238
1260
  self._logger.debug("Entering worker context...")
1239
1261
  await self.setup()
1240
1262
 
1241
1263
  return self
1242
1264
 
1243
- async def __aexit__(self, *exc_info):
1265
+ async def __aexit__(self, *exc_info: Any) -> None:
1244
1266
  self._logger.debug("Exiting worker context...")
1245
1267
  await self.teardown(*exc_info)
1246
1268
 
1247
- def __repr__(self):
1269
+ def __repr__(self) -> str:
1248
1270
  return f"Worker(pool={self._work_pool_name!r}, name={self.name!r})"
1249
1271
 
1250
1272
  def _event_resource(self):
@@ -13,6 +13,7 @@ to poll for flow runs.
13
13
  For more information about work pools and workers,
14
14
  checkout out the [Prefect docs](/concepts/work-pools/).
15
15
  """
16
+ from __future__ import annotations
16
17
 
17
18
  import contextlib
18
19
  import os
@@ -30,7 +31,7 @@ import anyio
30
31
  import anyio.abc
31
32
  from pydantic import Field, field_validator
32
33
 
33
- from prefect._internal.schemas.validators import validate_command
34
+ from prefect._internal.schemas.validators import validate_working_dir
34
35
  from prefect.client.schemas import FlowRun
35
36
  from prefect.client.schemas.filters import (
36
37
  FlowRunFilter,
@@ -85,19 +86,21 @@ class ProcessJobConfiguration(BaseJobConfiguration):
85
86
 
86
87
  @field_validator("working_dir")
87
88
  @classmethod
88
- def validate_command(cls, v: str) -> str:
89
- return validate_command(v)
89
+ def validate_working_dir(cls, v: Path | str | None) -> Path | None:
90
+ if isinstance(v, str):
91
+ return validate_working_dir(v)
92
+ return v
90
93
 
91
94
  def prepare_for_flow_run(
92
95
  self,
93
96
  flow_run: "FlowRun",
94
97
  deployment: Optional["DeploymentResponse"] = None,
95
98
  flow: Optional["Flow"] = None,
96
- ):
99
+ ) -> None:
97
100
  super().prepare_for_flow_run(flow_run, deployment, flow)
98
101
 
99
- self.env = {**os.environ, **self.env}
100
- self.command = (
102
+ self.env: dict[str, str | None] = {**os.environ, **self.env}
103
+ self.command: str | None = (
101
104
  f"{get_sys_executable()} -m prefect.engine"
102
105
  if self.command == self._base_flow_run_command()
103
106
  else self.command
@@ -134,10 +137,12 @@ class ProcessWorkerResult(BaseWorkerResult):
134
137
  """Contains information about the final state of a completed process"""
135
138
 
136
139
 
137
- class ProcessWorker(BaseWorker):
140
+ class ProcessWorker(
141
+ BaseWorker[ProcessJobConfiguration, ProcessVariables, ProcessWorkerResult]
142
+ ):
138
143
  type = "process"
139
- job_configuration = ProcessJobConfiguration
140
- job_configuration_variables = ProcessVariables
144
+ job_configuration: type[ProcessJobConfiguration] = ProcessJobConfiguration
145
+ job_configuration_variables: type[ProcessVariables] | None = ProcessVariables
141
146
 
142
147
  _description = (
143
148
  "Execute flow runs as subprocesses on a worker. Works well for local execution"
@@ -152,7 +157,7 @@ class ProcessWorker(BaseWorker):
152
157
  run_once: bool = False,
153
158
  with_healthcheck: bool = False,
154
159
  printer: Callable[..., None] = print,
155
- ):
160
+ ) -> None:
156
161
  """
157
162
  Starts the worker and runs the main worker loops.
158
163
 
@@ -241,8 +246,8 @@ class ProcessWorker(BaseWorker):
241
246
  self,
242
247
  flow_run: FlowRun,
243
248
  configuration: ProcessJobConfiguration,
244
- task_status: Optional[anyio.abc.TaskStatus] = None,
245
- ):
249
+ task_status: Optional[anyio.abc.TaskStatus[int]] = None,
250
+ ) -> ProcessWorkerResult:
246
251
  command = configuration.command
247
252
  if not command:
248
253
  command = f"{get_sys_executable()} -m prefect.engine"
@@ -322,7 +327,7 @@ class ProcessWorker(BaseWorker):
322
327
  self,
323
328
  infrastructure_pid: str,
324
329
  grace_seconds: int = 30,
325
- ):
330
+ ) -> None:
326
331
  hostname, pid = _parse_infrastructure_pid(infrastructure_pid)
327
332
 
328
333
  if hostname != socket.gethostname():
@@ -372,7 +377,7 @@ class ProcessWorker(BaseWorker):
372
377
  # process ended right after the check above.
373
378
  return
374
379
 
375
- async def check_for_cancelled_flow_runs(self):
380
+ async def check_for_cancelled_flow_runs(self) -> list["FlowRun"]:
376
381
  if not self.is_setup:
377
382
  raise RuntimeError(
378
383
  "Worker is not set up. Please make sure you are running this worker "
@@ -429,7 +434,7 @@ class ProcessWorker(BaseWorker):
429
434
 
430
435
  return cancelling_flow_runs
431
436
 
432
- async def cancel_run(self, flow_run: "FlowRun"):
437
+ async def cancel_run(self, flow_run: "FlowRun") -> None:
433
438
  run_logger = self.get_flow_run_logger(flow_run)
434
439
 
435
440
  try:
prefect/workers/server.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import Union
1
+ from typing import Any
2
2
 
3
3
  import uvicorn
4
4
  import uvicorn.server
@@ -10,14 +10,13 @@ from prefect.settings import (
10
10
  PREFECT_WORKER_WEBSERVER_PORT,
11
11
  )
12
12
  from prefect.workers.base import BaseWorker
13
- from prefect.workers.process import ProcessWorker
14
13
 
15
14
 
16
15
  def build_healthcheck_server(
17
- worker: Union[BaseWorker, ProcessWorker],
16
+ worker: BaseWorker[Any, Any, Any],
18
17
  query_interval_seconds: float,
19
18
  log_level: str = "error",
20
- ):
19
+ ) -> uvicorn.Server:
21
20
  """
22
21
  Build a healthcheck FastAPI server for a worker.
23
22
 
@@ -54,7 +53,7 @@ def build_healthcheck_server(
54
53
 
55
54
 
56
55
  def start_healthcheck_server(
57
- worker: Union[BaseWorker, ProcessWorker],
56
+ worker: BaseWorker[Any, Any, Any],
58
57
  query_interval_seconds: float,
59
58
  log_level: str = "error",
60
59
  ) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prefect-client
3
- Version: 3.1.12
3
+ Version: 3.1.14
4
4
  Summary: Workflow orchestration and management.
5
5
  Home-page: https://www.prefect.io
6
6
  Author: Prefect Technologies, Inc.
@@ -45,7 +45,7 @@ Requires-Dist: packaging<24.3,>=21.3
45
45
  Requires-Dist: pathspec>=0.8.0
46
46
  Requires-Dist: pendulum<4,>=3.0.0
47
47
  Requires-Dist: prometheus-client>=0.20.0
48
- Requires-Dist: pydantic!=2.10.0,<3.0.0,>=2.7
48
+ Requires-Dist: pydantic!=2.10.0,<3.0.0,>=2.9
49
49
  Requires-Dist: pydantic-core<3.0.0,>=2.12.0
50
50
  Requires-Dist: pydantic-extra-types<3.0.0,>=2.8.2
51
51
  Requires-Dist: pydantic-settings>2.2.1