prefect-client 3.4.1.dev3__py3-none-any.whl → 3.4.1.dev5__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.
prefect/_build_info.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # Generated by versioningit
2
- __version__ = "3.4.1.dev3"
3
- __build_date__ = "2025-05-06 08:08:45.922388+00:00"
4
- __git_commit__ = "3232581c69f5c912aa32787ca327ae549bc6eac7"
2
+ __version__ = "3.4.1.dev5"
3
+ __build_date__ = "2025-05-08 08:08:59.726919+00:00"
4
+ __git_commit__ = "7e7a18743f5fd0bd4e513ca22c5408e7176390b8"
5
5
  __dirty__ = False
@@ -30,7 +30,7 @@ def _get_uv_path() -> str:
30
30
  import uv
31
31
 
32
32
  uv_path = uv.find_uv_bin()
33
- except (ImportError, ModuleNotFoundError):
33
+ except (ImportError, ModuleNotFoundError, FileNotFoundError):
34
34
  uv_path = "uv"
35
35
 
36
36
  return uv_path
@@ -10,7 +10,6 @@ from __future__ import annotations
10
10
 
11
11
  import datetime
12
12
  import os
13
- import re
14
13
  import urllib.parse
15
14
  import warnings
16
15
  from collections.abc import Iterable, Mapping, MutableMapping
@@ -36,59 +35,6 @@ M = TypeVar("M", bound=Mapping[str, Any])
36
35
  MM = TypeVar("MM", bound=MutableMapping[str, Any])
37
36
 
38
37
 
39
- LOWERCASE_LETTERS_NUMBERS_AND_DASHES_ONLY_REGEX = "^[a-z0-9-]*$"
40
- LOWERCASE_LETTERS_NUMBERS_AND_UNDERSCORES_REGEX = "^[a-z0-9_]*$"
41
-
42
-
43
- @overload
44
- def raise_on_name_alphanumeric_dashes_only(
45
- value: str, field_name: str = ...
46
- ) -> str: ...
47
-
48
-
49
- @overload
50
- def raise_on_name_alphanumeric_dashes_only(
51
- value: None, field_name: str = ...
52
- ) -> None: ...
53
-
54
-
55
- def raise_on_name_alphanumeric_dashes_only(
56
- value: Optional[str], field_name: str = "value"
57
- ) -> Optional[str]:
58
- if value is not None and not bool(
59
- re.match(LOWERCASE_LETTERS_NUMBERS_AND_DASHES_ONLY_REGEX, value)
60
- ):
61
- raise ValueError(
62
- f"{field_name} must only contain lowercase letters, numbers, and dashes."
63
- )
64
- return value
65
-
66
-
67
- @overload
68
- def raise_on_name_alphanumeric_underscores_only(
69
- value: str, field_name: str = ...
70
- ) -> str: ...
71
-
72
-
73
- @overload
74
- def raise_on_name_alphanumeric_underscores_only(
75
- value: None, field_name: str = ...
76
- ) -> None: ...
77
-
78
-
79
- def raise_on_name_alphanumeric_underscores_only(
80
- value: Optional[str], field_name: str = "value"
81
- ) -> Optional[str]:
82
- if value is not None and not re.match(
83
- LOWERCASE_LETTERS_NUMBERS_AND_UNDERSCORES_REGEX, value
84
- ):
85
- raise ValueError(
86
- f"{field_name} must only contain lowercase letters, numbers, and"
87
- " underscores."
88
- )
89
- return value
90
-
91
-
92
38
  def validate_values_conform_to_schema(
93
39
  values: Optional[Mapping[str, Any]],
94
40
  schema: Optional[Mapping[str, Any]],
@@ -610,47 +556,3 @@ def validate_working_dir(v: Optional[Path | str]) -> Optional[Path]:
610
556
  if isinstance(v, str):
611
557
  return relative_path_to_current_platform(v)
612
558
  return v
613
-
614
-
615
- ### UNCATEGORIZED VALIDATORS ###
616
-
617
- # the above categories seem to be getting a bit unwieldy, so this is a temporary
618
- # catch-all for validators until we organize these into files
619
-
620
-
621
- @overload
622
- def validate_block_document_name(value: str) -> str: ...
623
-
624
-
625
- @overload
626
- def validate_block_document_name(value: None) -> None: ...
627
-
628
-
629
- def validate_block_document_name(value: Optional[str]) -> Optional[str]:
630
- if value is not None:
631
- raise_on_name_alphanumeric_dashes_only(value, field_name="Block document name")
632
- return value
633
-
634
-
635
- def validate_artifact_key(value: str) -> str:
636
- raise_on_name_alphanumeric_dashes_only(value, field_name="Artifact key")
637
- return value
638
-
639
-
640
- @overload
641
- def validate_variable_name(value: str) -> str: ...
642
-
643
-
644
- @overload
645
- def validate_variable_name(value: None) -> None: ...
646
-
647
-
648
- def validate_variable_name(value: Optional[str]) -> Optional[str]:
649
- if value is not None:
650
- raise_on_name_alphanumeric_underscores_only(value, field_name="Variable name")
651
- return value
652
-
653
-
654
- def validate_block_type_slug(value: str):
655
- raise_on_name_alphanumeric_dashes_only(value, field_name="Block type slug")
656
- return value
@@ -12,12 +12,8 @@ from prefect._internal.schemas.bases import ActionBaseModel
12
12
  from prefect._internal.schemas.validators import (
13
13
  convert_to_strings,
14
14
  remove_old_deployment_fields,
15
- validate_artifact_key,
16
- validate_block_document_name,
17
- validate_block_type_slug,
18
15
  validate_name_present_on_nonanonymous_blocks,
19
16
  validate_schedule_max_scheduled_runs,
20
- validate_variable_name,
21
17
  )
22
18
  from prefect.client.schemas.objects import (
23
19
  StateDetails,
@@ -34,7 +30,6 @@ from prefect.client.schemas.schedules import (
34
30
  from prefect.schedules import Schedule
35
31
  from prefect.settings import PREFECT_DEPLOYMENT_SCHEDULE_MAX_SCHEDULED_RUNS
36
32
  from prefect.types import (
37
- MAX_VARIABLE_NAME_LENGTH,
38
33
  DateTime,
39
34
  KeyValueLabelsField,
40
35
  Name,
@@ -45,6 +40,12 @@ from prefect.types import (
45
40
  PositiveInteger,
46
41
  StrictVariableValue,
47
42
  )
43
+ from prefect.types.names import (
44
+ ArtifactKey,
45
+ BlockDocumentName,
46
+ BlockTypeSlug,
47
+ VariableName,
48
+ )
48
49
  from prefect.utilities.collections import visit_collection
49
50
  from prefect.utilities.pydantic import get_class_fields_only
50
51
 
@@ -216,7 +217,7 @@ class DeploymentCreate(ActionBaseModel):
216
217
  flow_id: UUID = Field(..., description="The ID of the flow to deploy.")
217
218
  paused: Optional[bool] = Field(default=None)
218
219
  schedules: list[DeploymentScheduleCreate] = Field(
219
- default_factory=list,
220
+ default_factory=lambda: [],
220
221
  description="A list of schedules for the deployment.",
221
222
  )
222
223
  concurrency_limit: Optional[int] = Field(
@@ -537,7 +538,7 @@ class SavedSearchCreate(ActionBaseModel):
537
538
 
538
539
  name: str = Field(default=..., description="The name of the saved search.")
539
540
  filters: list[objects.SavedSearchFilter] = Field(
540
- default_factory=list, description="The filter set for the saved search."
541
+ default_factory=lambda: [], description="The filter set for the saved search."
541
542
  )
542
543
 
543
544
 
@@ -585,7 +586,7 @@ class BlockTypeCreate(ActionBaseModel):
585
586
  """Data used by the Prefect REST API to create a block type."""
586
587
 
587
588
  name: str = Field(default=..., description="A block type's name")
588
- slug: str = Field(default=..., description="A block type's slug")
589
+ slug: BlockTypeSlug = Field(default=..., description="A block type's slug")
589
590
  logo_url: Optional[objects.HttpUrl] = Field(
590
591
  default=None, description="Web URL for the block type's logo"
591
592
  )
@@ -601,9 +602,6 @@ class BlockTypeCreate(ActionBaseModel):
601
602
  description="A code snippet demonstrating use of the corresponding block",
602
603
  )
603
604
 
604
- # validators
605
- _validate_slug_format = field_validator("slug")(validate_block_type_slug)
606
-
607
605
 
608
606
  class BlockTypeUpdate(ActionBaseModel):
609
607
  """Data used by the Prefect REST API to update a block type."""
@@ -638,7 +636,7 @@ class BlockSchemaCreate(ActionBaseModel):
638
636
  class BlockDocumentCreate(ActionBaseModel):
639
637
  """Data used by the Prefect REST API to create a block document."""
640
638
 
641
- name: Optional[Name] = Field(
639
+ name: Optional[BlockDocumentName] = Field(
642
640
  default=None, description="The name of the block document"
643
641
  )
644
642
  data: dict[str, Any] = Field(
@@ -658,8 +656,6 @@ class BlockDocumentCreate(ActionBaseModel):
658
656
  ),
659
657
  )
660
658
 
661
- _validate_name_format = field_validator("name")(validate_block_document_name)
662
-
663
659
  @model_validator(mode="before")
664
660
  def validate_name_is_present_if_not_anonymous(
665
661
  cls, values: dict[str, Any]
@@ -814,7 +810,7 @@ class WorkQueueUpdate(ActionBaseModel):
814
810
  class ArtifactCreate(ActionBaseModel):
815
811
  """Data used by the Prefect REST API to create an artifact."""
816
812
 
817
- key: Optional[str] = Field(default=None)
813
+ key: Optional[ArtifactKey] = Field(default=None)
818
814
  type: Optional[str] = Field(default=None)
819
815
  description: Optional[str] = Field(default=None)
820
816
  data: Optional[Union[dict[str, Any], Any]] = Field(default=None)
@@ -822,8 +818,6 @@ class ArtifactCreate(ActionBaseModel):
822
818
  flow_run_id: Optional[UUID] = Field(default=None)
823
819
  task_run_id: Optional[UUID] = Field(default=None)
824
820
 
825
- _validate_artifact_format = field_validator("key")(validate_artifact_key)
826
-
827
821
 
828
822
  class ArtifactUpdate(ActionBaseModel):
829
823
  """Data used by the Prefect REST API to update an artifact."""
@@ -836,12 +830,7 @@ class ArtifactUpdate(ActionBaseModel):
836
830
  class VariableCreate(ActionBaseModel):
837
831
  """Data used by the Prefect REST API to create a Variable."""
838
832
 
839
- name: str = Field(
840
- default=...,
841
- description="The name of the variable",
842
- examples=["my_variable"],
843
- max_length=MAX_VARIABLE_NAME_LENGTH,
844
- )
833
+ name: VariableName = Field(default=...)
845
834
  value: StrictVariableValue = Field(
846
835
  default=...,
847
836
  description="The value of the variable",
@@ -849,19 +838,11 @@ class VariableCreate(ActionBaseModel):
849
838
  )
850
839
  tags: Optional[list[str]] = Field(default=None)
851
840
 
852
- # validators
853
- _validate_name_format = field_validator("name")(validate_variable_name)
854
-
855
841
 
856
842
  class VariableUpdate(ActionBaseModel):
857
843
  """Data used by the Prefect REST API to update a Variable."""
858
844
 
859
- name: Optional[str] = Field(
860
- default=None,
861
- description="The name of the variable",
862
- examples=["my_variable"],
863
- max_length=MAX_VARIABLE_NAME_LENGTH,
864
- )
845
+ name: Optional[VariableName] = Field(default=None)
865
846
  value: StrictVariableValue = Field(
866
847
  default=None,
867
848
  description="The value of the variable",
@@ -869,9 +850,6 @@ class VariableUpdate(ActionBaseModel):
869
850
  )
870
851
  tags: Optional[list[str]] = Field(default=None)
871
852
 
872
- # validators
873
- _validate_name_format = field_validator("name")(validate_variable_name)
874
-
875
853
 
876
854
  class GlobalConcurrencyLimitCreate(ActionBaseModel):
877
855
  """Data used by the Prefect REST API to create a global concurrency limit."""
@@ -19,6 +19,7 @@ from uuid import UUID, uuid4
19
19
 
20
20
  import orjson
21
21
  from pydantic import (
22
+ AfterValidator,
22
23
  ConfigDict,
23
24
  Discriminator,
24
25
  Field,
@@ -40,9 +41,7 @@ from prefect._internal.schemas.fields import CreatedBy, UpdatedBy
40
41
  from prefect._internal.schemas.validators import (
41
42
  get_or_create_run_name,
42
43
  list_length_50_or_less,
43
- raise_on_name_alphanumeric_dashes_only,
44
44
  set_run_policy_deprecated_fields,
45
- validate_block_document_name,
46
45
  validate_default_queue_id_not_none,
47
46
  validate_max_metadata_length,
48
47
  validate_name_present_on_nonanonymous_blocks,
@@ -61,6 +60,10 @@ from prefect.types import (
61
60
  StrictVariableValue,
62
61
  )
63
62
  from prefect.types._datetime import DateTime, now
63
+ from prefect.types.names import (
64
+ BlockDocumentName,
65
+ raise_on_name_alphanumeric_dashes_only,
66
+ )
64
67
  from prefect.utilities.asyncutils import run_coro_as_sync
65
68
  from prefect.utilities.collections import AutoEnum, visit_collection
66
69
  from prefect.utilities.names import generate_slug
@@ -1000,7 +1003,7 @@ class BlockSchema(ObjectBaseModel):
1000
1003
  class BlockDocument(ObjectBaseModel):
1001
1004
  """An ORM representation of a block document."""
1002
1005
 
1003
- name: Optional[Name] = Field(
1006
+ name: Optional[BlockDocumentName] = Field(
1004
1007
  default=None,
1005
1008
  description=(
1006
1009
  "The block document's name. Not required for anonymous block documents."
@@ -1029,8 +1032,6 @@ class BlockDocument(ObjectBaseModel):
1029
1032
  ),
1030
1033
  )
1031
1034
 
1032
- _validate_name_format = field_validator("name")(validate_block_document_name)
1033
-
1034
1035
  @model_validator(mode="before")
1035
1036
  @classmethod
1036
1037
  def validate_name_is_present_if_not_anonymous(
@@ -1133,7 +1134,8 @@ class Deployment(ObjectBaseModel):
1133
1134
  default=None, description="The concurrency limit for the deployment."
1134
1135
  )
1135
1136
  schedules: list[DeploymentSchedule] = Field(
1136
- default_factory=list, description="A list of schedules for the deployment."
1137
+ default_factory=lambda: [],
1138
+ description="A list of schedules for the deployment.",
1137
1139
  )
1138
1140
  job_variables: dict[str, Any] = Field(
1139
1141
  default_factory=dict,
@@ -1219,7 +1221,7 @@ class ConcurrencyLimit(ObjectBaseModel):
1219
1221
  )
1220
1222
  concurrency_limit: int = Field(default=..., description="The concurrency limit.")
1221
1223
  active_slots: list[UUID] = Field(
1222
- default_factory=list,
1224
+ default_factory=lambda: [],
1223
1225
  description="A list of active run ids using a concurrency slot",
1224
1226
  )
1225
1227
 
@@ -1300,7 +1302,8 @@ class SavedSearch(ObjectBaseModel):
1300
1302
 
1301
1303
  name: str = Field(default=..., description="The name of the saved search.")
1302
1304
  filters: list[SavedSearchFilter] = Field(
1303
- default_factory=list, description="The filter set for the saved search."
1305
+ default_factory=lambda: [],
1306
+ description="The filter set for the saved search.",
1304
1307
  )
1305
1308
 
1306
1309
 
@@ -1644,7 +1647,9 @@ class Variable(ObjectBaseModel):
1644
1647
 
1645
1648
  class FlowRunInput(ObjectBaseModel):
1646
1649
  flow_run_id: UUID = Field(description="The flow run ID associated with the input.")
1647
- key: str = Field(description="The key of the input.")
1650
+ key: Annotated[str, AfterValidator(raise_on_name_alphanumeric_dashes_only)] = Field(
1651
+ description="The key of the input."
1652
+ )
1648
1653
  value: str = Field(description="The value of the input.")
1649
1654
  sender: Optional[str] = Field(default=None, description="The sender of the input.")
1650
1655
 
@@ -1658,12 +1663,6 @@ class FlowRunInput(ObjectBaseModel):
1658
1663
  """
1659
1664
  return orjson.loads(self.value)
1660
1665
 
1661
- @field_validator("key", check_fields=False)
1662
- @classmethod
1663
- def validate_name_characters(cls, v: str) -> str:
1664
- raise_on_name_alphanumeric_dashes_only(v)
1665
- return v
1666
-
1667
1666
 
1668
1667
  class GlobalConcurrencyLimit(ObjectBaseModel):
1669
1668
  """An ORM representation of a global concurrency limit"""
prefect/events/clients.py CHANGED
@@ -251,8 +251,8 @@ class EventsClient(abc.ABC):
251
251
 
252
252
  async def __aexit__(
253
253
  self,
254
- exc_type: Optional[Type[Exception]],
255
- exc_val: Optional[Exception],
254
+ exc_type: Optional[Type[BaseException]],
255
+ exc_val: Optional[BaseException],
256
256
  exc_tb: Optional[TracebackType],
257
257
  ) -> None:
258
258
  del self._in_context
@@ -360,8 +360,8 @@ class PrefectEventsClient(EventsClient):
360
360
 
361
361
  async def __aexit__(
362
362
  self,
363
- exc_type: Optional[Type[Exception]],
364
- exc_val: Optional[Exception],
363
+ exc_type: Optional[Type[BaseException]],
364
+ exc_val: Optional[BaseException],
365
365
  exc_tb: Optional[TracebackType],
366
366
  ) -> None:
367
367
  self._websocket = None
prefect/flows.py CHANGED
@@ -27,6 +27,7 @@ from typing import (
27
27
  Coroutine,
28
28
  Generic,
29
29
  Iterable,
30
+ List,
30
31
  NoReturn,
31
32
  Optional,
32
33
  Protocol,
@@ -2309,8 +2310,9 @@ def load_flow_from_entrypoint(
2309
2310
  Extract a flow object from a script at an entrypoint by running all of the code in the file.
2310
2311
 
2311
2312
  Args:
2312
- entrypoint: a string in the format `<path_to_script>:<flow_func_name>` or a module path
2313
- to a flow function
2313
+ entrypoint: a string in the format `<path_to_script>:<flow_func_name>`
2314
+ or a string in the format `<path_to_script>:<class_name>.<flow_method_name>`
2315
+ or a module path to a flow function
2314
2316
  use_placeholder_flow: if True, use a placeholder Flow object if the actual flow object
2315
2317
  cannot be loaded from the entrypoint (e.g. dependencies are missing)
2316
2318
 
@@ -2700,26 +2702,55 @@ def load_placeholder_flow(entrypoint: str, raises: Exception) -> Flow[P, Any]:
2700
2702
 
2701
2703
  def safe_load_flow_from_entrypoint(entrypoint: str) -> Optional[Flow[P, Any]]:
2702
2704
  """
2703
- Load a flow from an entrypoint and return None if an exception is raised.
2705
+ Safely load a Prefect flow from an entrypoint string. Returns None if loading fails.
2704
2706
 
2705
2707
  Args:
2706
- entrypoint: a string in the format `<path_to_script>:<flow_func_name>`
2707
- or a module path to a flow function
2708
+ entrypoint (str): A string identifying the flow to load. Can be in one of the following formats:
2709
+ - `<path_to_script>:<flow_func_name>`
2710
+ - `<path_to_script>:<class_name>.<flow_method_name>`
2711
+ - `<module_path>.<flow_func_name>`
2712
+
2713
+ Returns:
2714
+ Optional[Flow]: The loaded Prefect flow object, or None if loading fails due to errors
2715
+ (e.g. unresolved dependencies, syntax errors, or missing objects).
2708
2716
  """
2709
- func_def, source_code = _entrypoint_definition_and_source(entrypoint)
2710
- path = None
2711
- if ":" in entrypoint:
2712
- path = entrypoint.rsplit(":")[0]
2717
+ func_or_cls_def, source_code, parts = _entrypoint_definition_and_source(entrypoint)
2718
+
2719
+ path = entrypoint.rsplit(":", maxsplit=1)[0] if ":" in entrypoint else None
2713
2720
  namespace = safe_load_namespace(source_code, filepath=path)
2714
- if func_def.name in namespace:
2715
- return namespace[func_def.name]
2716
- else:
2717
- # If the function is not in the namespace, if may be due to missing dependencies
2718
- # for the function. We will attempt to compile each annotation and default value
2719
- # and remove them from the function definition to see if the function can be
2720
- # compiled without them.
2721
2721
 
2722
- return _sanitize_and_load_flow(func_def, namespace)
2722
+ if parts[0] not in namespace:
2723
+ # If the object is not in the namespace, it may be due to missing dependencies
2724
+ # in annotations or default values. We will attempt to sanitize them by removing
2725
+ # anything that cannot be compiled, and then recompile the function or class.
2726
+ if isinstance(func_or_cls_def, (ast.FunctionDef, ast.AsyncFunctionDef)):
2727
+ return _sanitize_and_load_flow(func_or_cls_def, namespace)
2728
+ elif (
2729
+ isinstance(func_or_cls_def, ast.ClassDef)
2730
+ and len(parts) >= 2
2731
+ and func_or_cls_def.name == parts[0]
2732
+ ):
2733
+ method_name = parts[1]
2734
+ method_def = next(
2735
+ (
2736
+ stmt
2737
+ for stmt in func_or_cls_def.body
2738
+ if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef))
2739
+ and stmt.name == method_name
2740
+ ),
2741
+ None,
2742
+ )
2743
+ if method_def is not None:
2744
+ return _sanitize_and_load_flow(method_def, namespace)
2745
+ else:
2746
+ return None
2747
+
2748
+ obj = namespace.get(parts[0])
2749
+ for part in parts[1:]:
2750
+ obj = getattr(obj, part, None)
2751
+ if obj is None:
2752
+ return None
2753
+ return obj
2723
2754
 
2724
2755
 
2725
2756
  def _sanitize_and_load_flow(
@@ -2853,7 +2884,7 @@ def load_flow_arguments_from_entrypoint(
2853
2884
  or a module path to a flow function
2854
2885
  """
2855
2886
 
2856
- func_def, source_code = _entrypoint_definition_and_source(entrypoint)
2887
+ func_def, source_code, _ = _entrypoint_definition_and_source(entrypoint)
2857
2888
  path = None
2858
2889
  if ":" in entrypoint:
2859
2890
  path = entrypoint.rsplit(":")[0]
@@ -2930,26 +2961,45 @@ def is_entrypoint_async(entrypoint: str) -> bool:
2930
2961
  Returns:
2931
2962
  True if the function is asynchronous, False otherwise.
2932
2963
  """
2933
- func_def, _ = _entrypoint_definition_and_source(entrypoint)
2964
+ func_def, _, _ = _entrypoint_definition_and_source(entrypoint)
2934
2965
  return isinstance(func_def, ast.AsyncFunctionDef)
2935
2966
 
2936
2967
 
2937
2968
  def _entrypoint_definition_and_source(
2938
2969
  entrypoint: str,
2939
- ) -> Tuple[Union[ast.FunctionDef, ast.AsyncFunctionDef], str]:
2970
+ ) -> Tuple[Union[ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef], str, List[str]]:
2971
+ """
2972
+ Resolves and parses the source definition of a given entrypoint.
2973
+
2974
+ The entrypoint can be provided in one of the following formats:
2975
+ - '<path_to_script>:<flow_func_name>'
2976
+ - '<path_to_script>:<class_name>.<flow_method_name>'
2977
+ - '<module_path.to.flow_function>'
2978
+
2979
+ Returns:
2980
+ A tuple containing:
2981
+ - The AST node (FunctionDef, AsyncFunctionDef, or ClassDef) of the base object.
2982
+ - The full source code of the file or module as a string.
2983
+ - A list of attribute access parts from the object path (e.g., ['MyFlowClass', 'run']).
2984
+
2985
+ Raises:
2986
+ ValueError: If the module or target object cannot be found.
2987
+ """
2940
2988
  if ":" in entrypoint:
2941
- # Split by the last colon once to handle Windows paths with drive letters i.e C:\path\to\file.py:do_stuff
2942
- path, func_name = entrypoint.rsplit(":", maxsplit=1)
2989
+ path, object_path = entrypoint.rsplit(":", maxsplit=1)
2943
2990
  source_code = Path(path).read_text()
2944
2991
  else:
2945
- path, func_name = entrypoint.rsplit(".", maxsplit=1)
2992
+ path, object_path = entrypoint.rsplit(".", maxsplit=1)
2946
2993
  spec = importlib.util.find_spec(path)
2947
2994
  if not spec or not spec.origin:
2948
2995
  raise ValueError(f"Could not find module {path!r}")
2949
2996
  source_code = Path(spec.origin).read_text()
2950
2997
 
2951
2998
  parsed_code = ast.parse(source_code)
2952
- func_def = next(
2999
+ parts = object_path.split(".")
3000
+ base_name = parts[0]
3001
+
3002
+ base_def = next(
2953
3003
  (
2954
3004
  node
2955
3005
  for node in ast.walk(parsed_code)
@@ -2958,14 +3008,15 @@ def _entrypoint_definition_and_source(
2958
3008
  (
2959
3009
  ast.FunctionDef,
2960
3010
  ast.AsyncFunctionDef,
3011
+ ast.ClassDef, # flow can be staticmethod/classmethod
2961
3012
  ),
2962
3013
  )
2963
- and node.name == func_name
3014
+ and node.name == base_name
2964
3015
  ),
2965
3016
  None,
2966
3017
  )
2967
3018
 
2968
- if not func_def:
2969
- raise ValueError(f"Could not find flow {func_name!r} in {path!r}")
3019
+ if not base_def:
3020
+ raise ValueError(f"Could not find object {base_name!r} in {path!r}")
2970
3021
 
2971
- return func_def, source_code
3022
+ return base_def, source_code, parts
prefect/runner/runner.py CHANGED
@@ -89,9 +89,9 @@ from prefect.client.schemas.objects import (
89
89
  )
90
90
  from prefect.client.schemas.objects import Flow as APIFlow
91
91
  from prefect.events import DeploymentTriggerTypes, TriggerTypes
92
+ from prefect.events.clients import EventsClient, get_events_client
92
93
  from prefect.events.related import tags_as_related_resources
93
- from prefect.events.schemas.events import RelatedResource
94
- from prefect.events.utilities import emit_event
94
+ from prefect.events.schemas.events import Event, RelatedResource, Resource
95
95
  from prefect.exceptions import Abort, ObjectNotFound
96
96
  from prefect.flows import Flow, FlowStateHook, load_flow_from_flow_run
97
97
  from prefect.logging.loggers import PrefectLogAdapter, flow_run_logger, get_logger
@@ -224,6 +224,7 @@ class Runner:
224
224
  if self.heartbeat_seconds is not None and self.heartbeat_seconds < 30:
225
225
  raise ValueError("Heartbeat must be 30 seconds or greater.")
226
226
  self._heartbeat_task: asyncio.Task[None] | None = None
227
+ self._events_client: EventsClient = get_events_client(checkpoint_every=1)
227
228
 
228
229
  self._exit_stack = AsyncExitStack()
229
230
  self._limiter: anyio.CapacityLimiter | None = None
@@ -1005,7 +1006,7 @@ class Runner:
1005
1006
  )
1006
1007
 
1007
1008
  flow, deployment = await self._get_flow_and_deployment(flow_run)
1008
- self._emit_flow_run_cancelled_event(
1009
+ await self._emit_flow_run_cancelled_event(
1009
1010
  flow_run=flow_run, flow=flow, deployment=deployment
1010
1011
  )
1011
1012
  run_logger.info(f"Cancelled flow run '{flow_run.name}'!")
@@ -1064,14 +1065,18 @@ class Runner:
1064
1065
  related = [RelatedResource.model_validate(r) for r in related]
1065
1066
  related += tags_as_related_resources(set(tags))
1066
1067
 
1067
- emit_event(
1068
- event="prefect.flow-run.heartbeat",
1069
- resource={
1070
- "prefect.resource.id": f"prefect.flow-run.{flow_run.id}",
1071
- "prefect.resource.name": flow_run.name,
1072
- "prefect.version": __version__,
1073
- },
1074
- related=related,
1068
+ await self._events_client.emit(
1069
+ Event(
1070
+ event="prefect.flow-run.heartbeat",
1071
+ resource=Resource(
1072
+ {
1073
+ "prefect.resource.id": f"prefect.flow-run.{flow_run.id}",
1074
+ "prefect.resource.name": flow_run.name,
1075
+ "prefect.version": __version__,
1076
+ }
1077
+ ),
1078
+ related=related,
1079
+ )
1075
1080
  )
1076
1081
 
1077
1082
  def _event_resource(self):
@@ -1083,7 +1088,7 @@ class Runner:
1083
1088
  "prefect.version": __version__,
1084
1089
  }
1085
1090
 
1086
- def _emit_flow_run_cancelled_event(
1091
+ async def _emit_flow_run_cancelled_event(
1087
1092
  self,
1088
1093
  flow_run: "FlowRun",
1089
1094
  flow: "Optional[APIFlow]",
@@ -1118,10 +1123,12 @@ class Runner:
1118
1123
  related = [RelatedResource.model_validate(r) for r in related]
1119
1124
  related += tags_as_related_resources(set(tags))
1120
1125
 
1121
- emit_event(
1122
- event="prefect.runner.cancelled-flow-run",
1123
- resource=self._event_resource(),
1124
- related=related,
1126
+ await self._events_client.emit(
1127
+ Event(
1128
+ event="prefect.runner.cancelled-flow-run",
1129
+ resource=Resource(self._event_resource()),
1130
+ related=related,
1131
+ )
1125
1132
  )
1126
1133
  self._logger.debug(f"Emitted flow run heartbeat event for {flow_run.id}")
1127
1134
 
@@ -1502,6 +1509,7 @@ class Runner:
1502
1509
  )
1503
1510
  )
1504
1511
  await self._exit_stack.enter_async_context(self._client)
1512
+ await self._exit_stack.enter_async_context(self._events_client)
1505
1513
 
1506
1514
  if not hasattr(self, "_runs_task_group") or not self._runs_task_group:
1507
1515
  self._runs_task_group: anyio.abc.TaskGroup = anyio.create_task_group()
prefect/types/__init__.py CHANGED
@@ -1,13 +1,21 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from functools import partial
4
- from typing import Annotated, Any, Dict, List, Optional, Set, TypeVar, Union
4
+ from typing import Annotated, Any, Optional, TypeVar, Union
5
5
  from typing_extensions import Literal
6
6
  import orjson
7
7
  import pydantic
8
8
 
9
9
 
10
10
  from ._datetime import DateTime, Date
11
+ from .names import (
12
+ Name,
13
+ NameOrEmpty,
14
+ NonEmptyishName,
15
+ BANNED_CHARACTERS,
16
+ WITHOUT_BANNED_CHARACTERS,
17
+ MAX_VARIABLE_NAME_LENGTH,
18
+ )
11
19
  from pydantic import (
12
20
  BeforeValidator,
13
21
  Field,
@@ -21,7 +29,6 @@ from zoneinfo import available_timezones
21
29
 
22
30
  T = TypeVar("T")
23
31
 
24
- MAX_VARIABLE_NAME_LENGTH = 255
25
32
  MAX_VARIABLE_VALUE_LENGTH = 5000
26
33
 
27
34
  NonNegativeInteger = Annotated[int, Field(ge=0)]
@@ -39,37 +46,14 @@ TimeZone = Annotated[
39
46
  ]
40
47
 
41
48
 
42
- BANNED_CHARACTERS = ["/", "%", "&", ">", "<"]
43
-
44
- WITHOUT_BANNED_CHARACTERS = r"^[^" + "".join(BANNED_CHARACTERS) + "]+$"
45
- Name = Annotated[str, Field(pattern=WITHOUT_BANNED_CHARACTERS)]
46
-
47
- WITHOUT_BANNED_CHARACTERS_EMPTY_OK = r"^[^" + "".join(BANNED_CHARACTERS) + "]*$"
48
- NameOrEmpty = Annotated[str, Field(pattern=WITHOUT_BANNED_CHARACTERS_EMPTY_OK)]
49
-
50
-
51
- def non_emptyish(value: str) -> str:
52
- if not value.strip("' \""):
53
- raise ValueError("name cannot be an empty string")
54
-
55
- return value
56
-
57
-
58
- NonEmptyishName = Annotated[
59
- str,
60
- Field(pattern=WITHOUT_BANNED_CHARACTERS),
61
- BeforeValidator(non_emptyish),
62
- ]
63
-
64
-
65
49
  VariableValue = Union[
66
50
  StrictStr,
67
51
  StrictInt,
68
52
  StrictBool,
69
53
  StrictFloat,
70
54
  None,
71
- Dict[str, Any],
72
- List[Any],
55
+ dict[str, Any],
56
+ list[Any],
73
57
  ]
74
58
 
75
59
 
@@ -100,24 +84,24 @@ def cast_none_to_empty_dict(value: Any) -> dict[str, Any]:
100
84
 
101
85
 
102
86
  KeyValueLabels = Annotated[
103
- Dict[str, Union[StrictBool, StrictInt, StrictFloat, str]],
87
+ dict[str, Union[StrictBool, StrictInt, StrictFloat, str]],
104
88
  BeforeValidator(cast_none_to_empty_dict),
105
89
  ]
106
90
 
107
91
 
108
92
  ListOfNonEmptyStrings = Annotated[
109
- List[str],
93
+ list[str],
110
94
  BeforeValidator(lambda x: [str(s) for s in x if str(s).strip()]),
111
95
  ]
112
96
 
113
97
 
114
- class SecretDict(pydantic.Secret[Dict[str, Any]]):
98
+ class SecretDict(pydantic.Secret[dict[str, Any]]):
115
99
  pass
116
100
 
117
101
 
118
102
  def validate_set_T_from_delim_string(
119
- value: Union[str, T, Set[T], None], type_: type[T], delim: str | None = None
120
- ) -> Set[T]:
103
+ value: Union[str, T, set[T], None], type_: Any, delim: str | None = None
104
+ ) -> set[T]:
121
105
  """
122
106
  "no-info" before validator useful in scooping env vars
123
107
 
@@ -131,20 +115,20 @@ def validate_set_T_from_delim_string(
131
115
  delim = delim or ","
132
116
  if isinstance(value, str):
133
117
  return {T_adapter.validate_strings(s.strip()) for s in value.split(delim)}
134
- errors = []
118
+ errors: list[pydantic.ValidationError] = []
135
119
  try:
136
120
  return {T_adapter.validate_python(value)}
137
121
  except pydantic.ValidationError as e:
138
122
  errors.append(e)
139
123
  try:
140
- return TypeAdapter(Set[type_]).validate_python(value)
124
+ return TypeAdapter(set[type_]).validate_python(value)
141
125
  except pydantic.ValidationError as e:
142
126
  errors.append(e)
143
127
  raise ValueError(f"Invalid set[{type_}]: {errors}")
144
128
 
145
129
 
146
130
  ClientRetryExtraCodes = Annotated[
147
- Union[str, StatusCode, Set[StatusCode], None],
131
+ Union[str, StatusCode, set[StatusCode], None],
148
132
  BeforeValidator(partial(validate_set_T_from_delim_string, type_=StatusCode)),
149
133
  ]
150
134
 
@@ -170,11 +154,15 @@ KeyValueLabelsField = Annotated[
170
154
 
171
155
 
172
156
  __all__ = [
157
+ "BANNED_CHARACTERS",
158
+ "WITHOUT_BANNED_CHARACTERS",
173
159
  "ClientRetryExtraCodes",
174
160
  "Date",
175
161
  "DateTime",
176
162
  "LogLevel",
177
163
  "KeyValueLabelsField",
164
+ "MAX_VARIABLE_NAME_LENGTH",
165
+ "MAX_VARIABLE_VALUE_LENGTH",
178
166
  "NonNegativeInteger",
179
167
  "PositiveInteger",
180
168
  "ListOfNonEmptyStrings",
prefect/types/names.py ADDED
@@ -0,0 +1,139 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from functools import partial
5
+ from typing import Annotated, overload
6
+
7
+ from pydantic import AfterValidator, BeforeValidator, Field
8
+
9
+ LOWERCASE_LETTERS_NUMBERS_AND_DASHES_ONLY_REGEX = "^[a-z0-9-]*$"
10
+ LOWERCASE_LETTERS_NUMBERS_AND_UNDERSCORES_REGEX = "^[a-z0-9_]*$"
11
+ LOWERCASE_LETTERS_NUMBERS_AND_DASHES_OR_UNDERSCORES_REGEX = "^[a-z0-9-_]*$"
12
+
13
+
14
+ @overload
15
+ def raise_on_name_alphanumeric_dashes_only(
16
+ value: str, field_name: str = ...
17
+ ) -> str: ...
18
+
19
+
20
+ @overload
21
+ def raise_on_name_alphanumeric_dashes_only(
22
+ value: None, field_name: str = ...
23
+ ) -> None: ...
24
+
25
+
26
+ def raise_on_name_alphanumeric_dashes_only(
27
+ value: str | None, field_name: str = "value"
28
+ ) -> str | None:
29
+ if value is not None and not bool(
30
+ re.match(LOWERCASE_LETTERS_NUMBERS_AND_DASHES_ONLY_REGEX, value)
31
+ ):
32
+ raise ValueError(
33
+ f"{field_name} must only contain lowercase letters, numbers, and dashes."
34
+ )
35
+ return value
36
+
37
+
38
+ @overload
39
+ def raise_on_name_alphanumeric_underscores_only(
40
+ value: str, field_name: str = ...
41
+ ) -> str: ...
42
+
43
+
44
+ @overload
45
+ def raise_on_name_alphanumeric_underscores_only(
46
+ value: None, field_name: str = ...
47
+ ) -> None: ...
48
+
49
+
50
+ def raise_on_name_alphanumeric_underscores_only(
51
+ value: str | None, field_name: str = "value"
52
+ ) -> str | None:
53
+ if value is not None and not re.match(
54
+ LOWERCASE_LETTERS_NUMBERS_AND_UNDERSCORES_REGEX, value
55
+ ):
56
+ raise ValueError(
57
+ f"{field_name} must only contain lowercase letters, numbers, and"
58
+ " underscores."
59
+ )
60
+ return value
61
+
62
+
63
+ def raise_on_name_alphanumeric_dashes_underscores_only(
64
+ value: str, field_name: str = "value"
65
+ ) -> str:
66
+ if not re.match(LOWERCASE_LETTERS_NUMBERS_AND_DASHES_OR_UNDERSCORES_REGEX, value):
67
+ raise ValueError(
68
+ f"{field_name} must only contain lowercase letters, numbers, and"
69
+ " dashes or underscores."
70
+ )
71
+ return value
72
+
73
+
74
+ BANNED_CHARACTERS = ["/", "%", "&", ">", "<"]
75
+
76
+ WITHOUT_BANNED_CHARACTERS = r"^[^" + "".join(BANNED_CHARACTERS) + "]+$"
77
+ Name = Annotated[str, Field(pattern=WITHOUT_BANNED_CHARACTERS)]
78
+
79
+ WITHOUT_BANNED_CHARACTERS_EMPTY_OK = r"^[^" + "".join(BANNED_CHARACTERS) + "]*$"
80
+ NameOrEmpty = Annotated[str, Field(pattern=WITHOUT_BANNED_CHARACTERS_EMPTY_OK)]
81
+
82
+
83
+ def non_emptyish(value: str) -> str:
84
+ if not value.strip("' \""):
85
+ raise ValueError("name cannot be an empty string")
86
+
87
+ return value
88
+
89
+
90
+ NonEmptyishName = Annotated[
91
+ str,
92
+ Field(pattern=WITHOUT_BANNED_CHARACTERS),
93
+ BeforeValidator(non_emptyish),
94
+ ]
95
+
96
+
97
+ ### specific names
98
+
99
+ BlockDocumentName = Annotated[
100
+ Name,
101
+ AfterValidator(
102
+ partial(
103
+ raise_on_name_alphanumeric_dashes_only, field_name="Block document name"
104
+ )
105
+ ),
106
+ ]
107
+
108
+
109
+ BlockTypeSlug = Annotated[
110
+ str,
111
+ AfterValidator(
112
+ partial(raise_on_name_alphanumeric_dashes_only, field_name="Block type slug")
113
+ ),
114
+ ]
115
+
116
+ ArtifactKey = Annotated[
117
+ str,
118
+ AfterValidator(
119
+ partial(raise_on_name_alphanumeric_dashes_only, field_name="Artifact key")
120
+ ),
121
+ ]
122
+
123
+ MAX_VARIABLE_NAME_LENGTH = 255
124
+
125
+
126
+ VariableName = Annotated[
127
+ str,
128
+ AfterValidator(
129
+ partial(
130
+ raise_on_name_alphanumeric_dashes_underscores_only,
131
+ field_name="Variable name",
132
+ )
133
+ ),
134
+ Field(
135
+ max_length=MAX_VARIABLE_NAME_LENGTH,
136
+ description="The name of the variable",
137
+ examples=["my_variable"],
138
+ ),
139
+ ]
@@ -495,10 +495,11 @@ def parse_image_tag(name: str) -> tuple[str, Optional[str]]:
495
495
  """
496
496
  Parse Docker Image String
497
497
 
498
- - If a tag exists, this function parses and returns the image registry and tag,
498
+ - If a tag or digest exists, this function parses and returns the image registry and tag/digest,
499
499
  separately as a tuple.
500
500
  - Example 1: 'prefecthq/prefect:latest' -> ('prefecthq/prefect', 'latest')
501
501
  - Example 2: 'hostname.io:5050/folder/subfolder:latest' -> ('hostname.io:5050/folder/subfolder', 'latest')
502
+ - Example 3: 'prefecthq/prefect@sha256:abc123' -> ('prefecthq/prefect', 'sha256:abc123')
502
503
  - Supports parsing Docker Image strings that follow Docker Image Specification v1.1.0
503
504
  - Image building tools typically enforce this standard
504
505
 
@@ -506,26 +507,35 @@ def parse_image_tag(name: str) -> tuple[str, Optional[str]]:
506
507
  name (str): Name of Docker Image
507
508
 
508
509
  Return:
509
- tuple: image registry, image tag
510
+ tuple: image registry, image tag/digest
510
511
  """
511
512
  tag = None
512
513
  name_parts = name.split("/")
513
- # First handles the simplest image names (DockerHub-based, index-free, potentionally with a tag)
514
- # - Example: simplename:latest
514
+
515
+ # First handles the simplest image names (DockerHub-based, index-free, potentially with a tag or digest)
516
+ # - Example: simplename:latest or simplename@sha256:abc123
515
517
  if len(name_parts) == 1:
516
- if ":" in name_parts[0]:
518
+ if "@" in name_parts[0]:
519
+ image_name, tag = name_parts[0].split("@")
520
+ elif ":" in name_parts[0]:
517
521
  image_name, tag = name_parts[0].split(":")
522
+
518
523
  else:
519
524
  image_name = name_parts[0]
520
525
  else:
521
526
  # 1. Separates index (hostname.io or prefecthq) from path:tag (folder/subfolder:latest or prefect:latest)
522
- # 2. Separates path and tag (if tag exists)
523
- # 3. Reunites index and path (without tag) as image name
527
+ # 2. Separates path and tag/digest (if exists)
528
+ # 3. Reunites index and path (without tag/digest) as image name
524
529
  index_name = name_parts[0]
525
530
  image_path = "/".join(name_parts[1:])
526
- if ":" in image_path:
531
+
532
+ if "@" in image_path:
533
+ image_path, tag = image_path.split("@")
534
+ elif ":" in image_path:
527
535
  image_path, tag = image_path.split(":")
536
+
528
537
  image_name = f"{index_name}/{image_path}"
538
+
529
539
  return image_name, tag
530
540
 
531
541
 
@@ -145,17 +145,19 @@ def import_object(import_path: str) -> Any:
145
145
  - module.object
146
146
  - module:object
147
147
  - /path/to/script.py:object
148
+ - module:object.method
149
+ - /path/to/script.py:object.method
148
150
 
149
151
  This function is not thread safe as it modifies the 'sys' module during execution.
150
152
  """
151
153
  if ".py:" in import_path:
152
- script_path, object_name = import_path.rsplit(":", 1)
154
+ script_path, object_path = import_path.rsplit(":", 1)
153
155
  module = load_script_as_module(script_path)
154
156
  else:
155
157
  if ":" in import_path:
156
- module_name, object_name = import_path.rsplit(":", 1)
158
+ module_name, object_path = import_path.rsplit(":", 1)
157
159
  elif "." in import_path:
158
- module_name, object_name = import_path.rsplit(".", 1)
160
+ module_name, object_path = import_path.rsplit(".", 1)
159
161
  else:
160
162
  raise ValueError(
161
163
  f"Invalid format for object import. Received {import_path!r}."
@@ -163,7 +165,13 @@ def import_object(import_path: str) -> Any:
163
165
 
164
166
  module = load_module(module_name)
165
167
 
166
- return getattr(module, object_name)
168
+ # Handle nested object/method access
169
+ parts = object_path.split(".")
170
+ obj = module
171
+ for part in parts:
172
+ obj = getattr(obj, part)
173
+
174
+ return obj
167
175
 
168
176
 
169
177
  class DelayedImportErrorModule(ModuleType):
prefect/workers/base.py CHANGED
@@ -697,6 +697,25 @@ class BaseWorker(abc.ABC, Generic[C, V, R]):
697
697
  "Workers must implement a method for running submitted flow runs"
698
698
  )
699
699
 
700
+ async def _initiate_run(
701
+ self,
702
+ flow_run: "FlowRun",
703
+ configuration: C,
704
+ ) -> None:
705
+ """
706
+ This method is called by the worker to initiate a flow run and should return as
707
+ soon as possible.
708
+
709
+ This method is used in `.submit` to allow non-blocking submission of flows. For
710
+ workers that wait for completion in their `run` method, this method should be
711
+ implemented to return immediately.
712
+
713
+ If this method is not implemented, `.submit` will fall back to the `.run` method.
714
+ """
715
+ raise NotImplementedError(
716
+ "This worker has not implemented `_initiate_run`. Please use `run` instead."
717
+ )
718
+
700
719
  async def submit(
701
720
  self,
702
721
  flow: "Flow[..., FR]",
@@ -866,16 +885,19 @@ class BaseWorker(abc.ABC, Generic[C, V, R]):
866
885
  try:
867
886
  # Call the implementation-specific run method with the constructed configuration. This is where the
868
887
  # rubber meets the road.
869
- result = await self.run(flow_run, configuration)
870
-
871
- if result.status_code != 0:
872
- await self._propose_crashed_state(
873
- flow_run,
874
- (
875
- "Flow run infrastructure exited with non-zero status code"
876
- f" {result.status_code}."
877
- ),
878
- )
888
+ try:
889
+ await self._initiate_run(flow_run, configuration)
890
+ except NotImplementedError:
891
+ result = await self.run(flow_run, configuration)
892
+
893
+ if result.status_code != 0:
894
+ await self._propose_crashed_state(
895
+ flow_run,
896
+ (
897
+ "Flow run infrastructure exited with non-zero status code"
898
+ f" {result.status_code}."
899
+ ),
900
+ )
879
901
  except Exception as exc:
880
902
  # This flow run was being submitted and did not start successfully
881
903
  logger.exception(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prefect-client
3
- Version: 3.4.1.dev3
3
+ Version: 3.4.1.dev5
4
4
  Summary: Workflow orchestration and management.
5
5
  Project-URL: Changelog, https://github.com/PrefectHQ/prefect/releases
6
6
  Project-URL: Documentation, https://docs.prefect.io
@@ -1,7 +1,7 @@
1
1
  prefect/.prefectignore,sha256=awSprvKT0vI8a64mEOLrMxhxqcO-b0ERQeYpA2rNKVQ,390
2
2
  prefect/__init__.py,sha256=iCdcC5ZmeewikCdnPEP6YBAjPNV5dvfxpYCTpw30Hkw,3685
3
3
  prefect/__main__.py,sha256=WFjw3kaYJY6pOTA7WDOgqjsz8zUEUZHCcj3P5wyVa-g,66
4
- prefect/_build_info.py,sha256=8pmC8mu0pHkpPwKqc-qyey5T5tf2n8h7uCl42Nk08Qk,185
4
+ prefect/_build_info.py,sha256=X9CjltSx8CpEa5y-u1RGzeOI83l9BsXPZ3myZoXzOTg,185
5
5
  prefect/_result_records.py,sha256=S6QmsODkehGVSzbMm6ig022PYbI6gNKz671p_8kBYx4,7789
6
6
  prefect/_versioning.py,sha256=YqR5cxXrY4P6LM1Pmhd8iMo7v_G2KJpGNdsf4EvDFQ0,14132
7
7
  prefect/_waiters.py,sha256=Ia2ITaXdHzevtyWIgJoOg95lrEXQqNEOquHvw3T33UQ,9026
@@ -15,7 +15,7 @@ prefect/exceptions.py,sha256=wZLQQMRB_DyiYkeEdIC5OKwbba5A94Dlnics-lrWI7A,11581
15
15
  prefect/filesystems.py,sha256=v5YqGB4uXf9Ew2VuB9VCSkawvYMMVvEtZf7w1VmAmr8,18036
16
16
  prefect/flow_engine.py,sha256=hZpTYEtwTPMtwVoTCrfD93igN7rlKeG_0kyCvdU4aYE,58876
17
17
  prefect/flow_runs.py,sha256=d3jfmrIPP3C19IJREvpkuN6fxksX3Lzo-LlHOB-_E2I,17419
18
- prefect/flows.py,sha256=dxy3xfNexZ1NCEYM4UdHI0dQsn6QQJCPx0XEEoYFOBk,118900
18
+ prefect/flows.py,sha256=3dm4IjIpoKHqgdQACeZPvqbqoRd7XjSnsCyOC3nm5H8,120916
19
19
  prefect/futures.py,sha256=5wVHLtniwG2au0zuxM-ucqo08x0B5l6e8Z1Swbe8R9s,23720
20
20
  prefect/main.py,sha256=8V-qLB4GjEVCkGRgGXeaIk-JIXY8Z9FozcNluj4Sm9E,2589
21
21
  prefect/plugins.py,sha256=FPRLR2mWVBMuOnlzeiTD9krlHONZH2rtYLD753JQDNQ,2516
@@ -33,7 +33,7 @@ prefect/transactions.py,sha256=uIoPNudzJzH6NrMJhrgr5lyh6JxOJQqT1GvrXt69yNw,26068
33
33
  prefect/variables.py,sha256=dCK3vX7TbkqXZhnNT_v7rcGh3ISRqoR6pJVLpoll3Js,8342
34
34
  prefect/_experimental/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
35
  prefect/_experimental/lineage.py,sha256=8LssReoq7eLtQScUCu-7FCtrWoRZstXKRdpO0PxgbKg,9958
36
- prefect/_experimental/bundles/__init__.py,sha256=9e7L7drTpHG82fnr6kuABkpk3SdqUNF-8HB2y6vD5U4,7108
36
+ prefect/_experimental/bundles/__init__.py,sha256=rrYdykd2XWNWi0g9ZJmBzh8wMZrRo0F1dnoBtzNyI0A,7127
37
37
  prefect/_experimental/bundles/execute.py,sha256=1_v3tGFQlQEj9eOLsGG5EHtNcwyxmOU-LYYoK1LP9pA,635
38
38
  prefect/_experimental/sla/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
39
  prefect/_experimental/sla/client.py,sha256=XTkYHFZiBy_O7RgUyGEdl9MxaHP-6fEAKBk3ksNQobU,3611
@@ -66,7 +66,7 @@ prefect/_internal/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
66
66
  prefect/_internal/schemas/bases.py,sha256=JqcZazL5Cp2hZ8Hssu8R2SVXRxHfbdRbTqmvwDYSzyk,4291
67
67
  prefect/_internal/schemas/fields.py,sha256=m4LrFNz8rA9uBhMk9VyQT6FIXmV_EVAW92hdXeSvHbY,837
68
68
  prefect/_internal/schemas/serializers.py,sha256=G_RGHfObjisUiRvd29p-zc6W4bwt5rE1OdR6TXNrRhQ,825
69
- prefect/_internal/schemas/validators.py,sha256=-iHt659pMvu6DgPqHTfcRtDUF9tO4yt7HFS391HhRuQ,19175
69
+ prefect/_internal/schemas/validators.py,sha256=bOtuOYHWfRo-i6zqkE-wvCMXQ3Yww-itj86QLp3yu3Y,16681
70
70
  prefect/_vendor/croniter/__init__.py,sha256=NUFzdbyPcTQhIOFtzmFM0nbClAvBbKh2mlnTBa6NfHU,523
71
71
  prefect/_vendor/croniter/croniter.py,sha256=eJ2HzStNAYV-vNiLOgDXl4sYWWHOsSA0dgwbkQoguhY,53009
72
72
  prefect/blocks/__init__.py,sha256=D0hB72qMfgqnBB2EMZRxUxlX9yLfkab5zDChOwJZmkY,220
@@ -112,9 +112,9 @@ prefect/client/orchestration/_variables/client.py,sha256=wKBbZBLGgs5feDCil-xxKt3
112
112
  prefect/client/orchestration/_work_pools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
113
113
  prefect/client/orchestration/_work_pools/client.py,sha256=s1DfUQQBgB2sLiVVPhLNTlkueUDE6uFsh4mAzcSA1OE,19881
114
114
  prefect/client/schemas/__init__.py,sha256=InZcDzdeWA2oaV0TlyvoMcyLcbi_aaqU1U9D6Gx-eoU,2747
115
- prefect/client/schemas/actions.py,sha256=EigGRTOwa_aWBMfqiTvNaUO8e78M1kIxorEzp1bigcI,33148
115
+ prefect/client/schemas/actions.py,sha256=E46Mdq7vAq8hhYmMj6zqUF20uAPXZricViZcIYmgEf0,32443
116
116
  prefect/client/schemas/filters.py,sha256=qa--NNZduuSOcL1xw-YMd4FVIKMrDnBwPPY4m5Di0GA,35963
117
- prefect/client/schemas/objects.py,sha256=NevDxRA0TEmM0W01q-FMOHVsW7c76vuwk9puJpFneZE,57850
117
+ prefect/client/schemas/objects.py,sha256=pmu3CGQ62LYHgS0bEDS_s2XDwtkuR17BYbM5_6vGcWg,57755
118
118
  prefect/client/schemas/responses.py,sha256=Zdcx7jlIaluEa2uYIOE5mK1HsJvWPErRAcaWM20oY_I,17336
119
119
  prefect/client/schemas/schedules.py,sha256=sxLFk0SmFY7X1Y9R9HyGDqOS3U5NINBWTciUU7vTTic,14836
120
120
  prefect/client/schemas/sorting.py,sha256=L-2Mx-igZPtsUoRUguTcG3nIEstMEMPD97NwPM2Ox5s,2579
@@ -148,7 +148,7 @@ prefect/docker/__init__.py,sha256=z6wdc6UFfiBG2jb9Jk64uCWVM04JKVWeVyDWwuuon8M,52
148
148
  prefect/docker/docker_image.py,sha256=bR_pEq5-FDxlwTj8CP_7nwZ_MiGK6KxIi8v7DRjy1Kg,3138
149
149
  prefect/events/__init__.py,sha256=GtKl2bE--pJduTxelH2xy7SadlLJmmis8WR1EYixhuA,2094
150
150
  prefect/events/actions.py,sha256=A7jS8bo4zWGnrt3QfSoQs0uYC1xfKXio3IfU0XtTb5s,9129
151
- prefect/events/clients.py,sha256=gp3orepQav99303OC-zK6uz3dpyLlLpQ9ZWJEDol0cs,27597
151
+ prefect/events/clients.py,sha256=e3A6cKxi-fG2TkFedaRuC472hIM3VgaVxI6mcPP41kY,27613
152
152
  prefect/events/filters.py,sha256=2hVfzc3Rdgy0mBHDutWxT__LJY0zpVM8greWX3y6kjM,8233
153
153
  prefect/events/related.py,sha256=CTeexYUmmA93V4gsR33GIFmw-SS-X_ouOpRg-oeq-BU,6672
154
154
  prefect/events/utilities.py,sha256=ww34bTMENCNwcp6RhhgzG0KgXOvKGe0MKmBdSJ8NpZY,3043
@@ -185,7 +185,7 @@ prefect/logging/loggers.py,sha256=rwFJv0i3dhdKr25XX-xUkQy4Vv4dy18bTy366jrC0OQ,12
185
185
  prefect/logging/logging.yml,sha256=tT7gTyC4NmngFSqFkCdHaw7R0GPNPDDsTCGZQByiJAQ,3169
186
186
  prefect/runner/__init__.py,sha256=pQBd9wVrUVUDUFJlgiweKSnbahoBZwqnd2O2jkhrULY,158
187
187
  prefect/runner/_observers.py,sha256=PpyXQL5bjp86AnDFEzcFPS5ayL6ExqcYgyuBMMQCO9Q,2183
188
- prefect/runner/runner.py,sha256=DFgZQTkKwmCDMmfA640xY1oTOCURzTOo7HOtwQxRVwA,59443
188
+ prefect/runner/runner.py,sha256=04-SK3rP4nd2PLNs5wSiRbtycnq7Lds8cBsWWM6V6NM,59865
189
189
  prefect/runner/server.py,sha256=YRYFNoYddA9XfiTIYtudxrnD1vCX-PaOLhvyGUOb9AQ,11966
190
190
  prefect/runner/storage.py,sha256=n-65YoEf7KNVInnmMPeP5TVFJOa2zOS8w9en9MHi6uo,31328
191
191
  prefect/runner/submit.py,sha256=qOEj-NChQ6RYFV35hHEVMTklrNmKwaGs2mR78ku9H0o,9474
@@ -276,9 +276,10 @@ prefect/telemetry/logging.py,sha256=ktIVTXbdZ46v6fUhoHNidFrpvpNJR-Pj-hQ4V9b40W4,
276
276
  prefect/telemetry/processors.py,sha256=jw6j6LviOVxw3IBJe7cSjsxFk0zzY43jUmy6C9pcfCE,2272
277
277
  prefect/telemetry/run_telemetry.py,sha256=_FbjiPqPemu4xvZuI2YBPwXeRJ2BcKRJ6qgO4UMzKKE,8571
278
278
  prefect/telemetry/services.py,sha256=DxgNNDTeWNtHBtioX8cjua4IrCbTiJJdYecx-gugg-w,2358
279
- prefect/types/__init__.py,sha256=yBjKxiQmSC7jXoo0UNmM3KZil1NBFS-BWGPfwSEaoJo,4621
279
+ prefect/types/__init__.py,sha256=vzFQspL0xeqQVW3rtXdBk1hKi_nlzvg8Qaf4jyQ95v0,4261
280
280
  prefect/types/_datetime.py,sha256=ZE-4YK5XJuyxnp5pqldZwtIjkxCpxDGnCSfZiTl7-TU,7566
281
281
  prefect/types/entrypoint.py,sha256=2FF03-wLPgtnqR_bKJDB2BsXXINPdu8ptY9ZYEZnXg8,328
282
+ prefect/types/names.py,sha256=CMMZD928iiod2UvB0qrsfXEBC5jj_bO0ge1fFXcrtgM,3450
282
283
  prefect/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
283
284
  prefect/utilities/_ast.py,sha256=sgEPUWElih-3cp4PoAy1IOyPtu8E27lL0Dldf3ijnYY,4905
284
285
  prefect/utilities/_deprecated.py,sha256=b3pqRSoFANdVJAc8TJkygBcP-VjZtLJUxVIWC7kwspI,1303
@@ -291,12 +292,12 @@ prefect/utilities/collections.py,sha256=c3nPLPWqIZQQdNuHs_nrbQJwuhQSX4ivUl-h9Ltz
291
292
  prefect/utilities/compat.py,sha256=nnPA3lf2f4Y-l645tYFFNmj5NDPaYvjqa9pbGKZ3WKE,582
292
293
  prefect/utilities/context.py,sha256=23SDMgdt07SjmB1qShiykHfGgiv55NBzdbMXM3fE9CI,1447
293
294
  prefect/utilities/dispatch.py,sha256=u6GSGSO3_6vVoIqHVc849lsKkC-I1wUl6TX134GwRBo,6310
294
- prefect/utilities/dockerutils.py,sha256=WhVaa9g5LQuzBqvG3rJQQLGRneEP-37lVeJjrO6_VHg,21315
295
+ prefect/utilities/dockerutils.py,sha256=6DLVyzE195IzeQSWERiK1t3bDMnYBLe0zXIpMQ4r0c0,21659
295
296
  prefect/utilities/engine.py,sha256=LAqRMKM0lJphCHTMFKxRKNZzp_Y4l2PMUXmaFLdmvrQ,28951
296
297
  prefect/utilities/filesystem.py,sha256=Pwesv71PGFhf3lPa1iFyMqZZprBjy9nEKCVxTkf_hXw,5710
297
298
  prefect/utilities/generics.py,sha256=o77e8a5iwmrisOf42wLp2WI9YvSw2xDW4vFdpdEwr3I,543
298
299
  prefect/utilities/hashing.py,sha256=7jRy26s46IJAFRmVnCnoK9ek9N4p_UfXxQQvu2tW6dM,2589
299
- prefect/utilities/importtools.py,sha256=u8kx7mzNPiM81Lyk57HLn6Z9kqA-gQ9y5KsYu4d6VhI,17734
300
+ prefect/utilities/importtools.py,sha256=Bgis-5EFaX8XekwiXa2Cr4jE76yiFBmp0mQ9iGZsVvU,17925
300
301
  prefect/utilities/math.py,sha256=UPIdJMP13lCU3o0Yz98o4VDw3LTkkrsOAsvAdA3Xifc,2954
301
302
  prefect/utilities/names.py,sha256=PcNp3IbSoJY6P3UiJDYDjpYQw6BYWtn6OarFDCq1dUE,1744
302
303
  prefect/utilities/processutils.py,sha256=k_VD41Q0EBz-DP2lN7AcOkFGpYH3ekKGk4YV_OuvQc8,16255
@@ -313,13 +314,13 @@ prefect/utilities/schema_tools/__init__.py,sha256=At3rMHd2g_Em2P3_dFQlFgqR_EpBwr
313
314
  prefect/utilities/schema_tools/hydration.py,sha256=NkRhWkNfxxFmVGhNDfmxdK_xeKaEhs3a42q83Sg9cT4,9436
314
315
  prefect/utilities/schema_tools/validation.py,sha256=Wix26IVR-ZJ32-6MX2pHhrwm3reB-Q4iB6_phn85OKE,10743
315
316
  prefect/workers/__init__.py,sha256=EaM1F0RZ-XIJaGeTKLsXDnfOPHzVWk5bk0_c4BVS44M,64
316
- prefect/workers/base.py,sha256=gqXHTFFhjIzCZoBh4FDlA8AAm4l2j6GW6wS7Q7Hbb20,61504
317
+ prefect/workers/base.py,sha256=_Puzm_f2Q7YLI89G_u9oM3esvwUWIKZ3fpfPqi-KMQk,62358
317
318
  prefect/workers/block.py,sha256=dPvG1jDGD5HSH7aM2utwtk6RaJ9qg13XjkA0lAIgQmY,287
318
319
  prefect/workers/cloud.py,sha256=dPvG1jDGD5HSH7aM2utwtk6RaJ9qg13XjkA0lAIgQmY,287
319
320
  prefect/workers/process.py,sha256=Yi5D0U5AQ51wHT86GdwtImXSefe0gJf3LGq4r4z9zwM,11090
320
321
  prefect/workers/server.py,sha256=2pmVeJZiVbEK02SO6BEZaBIvHMsn6G8LzjW8BXyiTtk,1952
321
322
  prefect/workers/utilities.py,sha256=VfPfAlGtTuDj0-Kb8WlMgAuOfgXCdrGAnKMapPSBrwc,2483
322
- prefect_client-3.4.1.dev3.dist-info/METADATA,sha256=gUapSwIlB6t5yjno6lP6BKGAOOMlI0egLFS582QAGbY,7471
323
- prefect_client-3.4.1.dev3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
324
- prefect_client-3.4.1.dev3.dist-info/licenses/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
325
- prefect_client-3.4.1.dev3.dist-info/RECORD,,
323
+ prefect_client-3.4.1.dev5.dist-info/METADATA,sha256=PUcsY0sXpjiBspwz8PP-QToO8lAjsegJsS62iCWM1so,7471
324
+ prefect_client-3.4.1.dev5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
325
+ prefect_client-3.4.1.dev5.dist-info/licenses/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
326
+ prefect_client-3.4.1.dev5.dist-info/RECORD,,