prefect-client 3.4.1.dev4__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.dev4"
3
- __build_date__ = "2025-05-07 08:09:04.443127+00:00"
4
- __git_commit__ = "15e90f75877628d769414cb3382d3aa5b30b50a5"
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
@@ -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,60 +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
- SLUG_REGEX = "^[a-z0-9]+([_-]+[a-z0-9]+)*$"
42
-
43
-
44
- @overload
45
- def raise_on_name_alphanumeric_dashes_only(
46
- value: str, field_name: str = ...
47
- ) -> str: ...
48
-
49
-
50
- @overload
51
- def raise_on_name_alphanumeric_dashes_only(
52
- value: None, field_name: str = ...
53
- ) -> None: ...
54
-
55
-
56
- def raise_on_name_alphanumeric_dashes_only(
57
- value: Optional[str], field_name: str = "value"
58
- ) -> Optional[str]:
59
- if value is not None and not bool(
60
- re.match(LOWERCASE_LETTERS_NUMBERS_AND_DASHES_ONLY_REGEX, value)
61
- ):
62
- raise ValueError(
63
- f"{field_name} must only contain lowercase letters, numbers, and dashes."
64
- )
65
- return value
66
-
67
-
68
- @overload
69
- def raise_on_name_alphanumeric_underscores_only(
70
- value: str, field_name: str = ...
71
- ) -> str: ...
72
-
73
-
74
- @overload
75
- def raise_on_name_alphanumeric_underscores_only(
76
- value: None, field_name: str = ...
77
- ) -> None: ...
78
-
79
-
80
- def raise_on_name_alphanumeric_underscores_only(
81
- value: Optional[str], field_name: str = "value"
82
- ) -> Optional[str]:
83
- if value is not None and not re.match(
84
- LOWERCASE_LETTERS_NUMBERS_AND_UNDERSCORES_REGEX, value
85
- ):
86
- raise ValueError(
87
- f"{field_name} must only contain lowercase letters, numbers, and"
88
- " underscores."
89
- )
90
- return value
91
-
92
-
93
38
  def validate_values_conform_to_schema(
94
39
  values: Optional[Mapping[str, Any]],
95
40
  schema: Optional[Mapping[str, Any]],
@@ -611,50 +556,3 @@ def validate_working_dir(v: Optional[Path | str]) -> Optional[Path]:
611
556
  if isinstance(v, str):
612
557
  return relative_path_to_current_platform(v)
613
558
  return v
614
-
615
-
616
- ### UNCATEGORIZED VALIDATORS ###
617
-
618
- # the above categories seem to be getting a bit unwieldy, so this is a temporary
619
- # catch-all for validators until we organize these into files
620
-
621
-
622
- @overload
623
- def validate_block_document_name(value: str) -> str: ...
624
-
625
-
626
- @overload
627
- def validate_block_document_name(value: None) -> None: ...
628
-
629
-
630
- def validate_block_document_name(value: Optional[str]) -> Optional[str]:
631
- if value is not None:
632
- raise_on_name_alphanumeric_dashes_only(value, field_name="Block document name")
633
- return value
634
-
635
-
636
- def validate_artifact_key(value: str) -> str:
637
- raise_on_name_alphanumeric_dashes_only(value, field_name="Artifact key")
638
- return value
639
-
640
-
641
- @overload
642
- def validate_variable_name(value: str) -> str: ...
643
-
644
-
645
- @overload
646
- def validate_variable_name(value: None) -> None: ...
647
-
648
-
649
- def validate_variable_name(value: Optional[str]) -> Optional[str]:
650
- if value is not None:
651
- if not bool(re.match(SLUG_REGEX, value)):
652
- raise ValueError(
653
- "Variable name must only contain lowercase letters, numbers, dashes, and underscores"
654
- )
655
- return value
656
-
657
-
658
- def validate_block_type_slug(value: str):
659
- raise_on_name_alphanumeric_dashes_only(value, field_name="Block type slug")
660
- 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/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
+ ]
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.dev4
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=QcFtoY6cMGvzPP_9d4qiiLL87zxdwx9Cl4dxKsa7CyA,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
@@ -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=Pe78HTN1TQQbpkLx66smzx2udv5rGxdaqn1ZsR1zlJs,19328
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
@@ -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
@@ -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.dev4.dist-info/METADATA,sha256=MGFEKoWTbDHQ_jlNcHTQ1oKdHbzNWuRs2tn0ETq9ZyU,7471
323
- prefect_client-3.4.1.dev4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
324
- prefect_client-3.4.1.dev4.dist-info/licenses/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
325
- prefect_client-3.4.1.dev4.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,,