prefect-client 3.1.10__py3-none-any.whl → 3.1.12__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 (141) hide show
  1. prefect/_experimental/lineage.py +7 -8
  2. prefect/_experimental/sla/__init__.py +0 -0
  3. prefect/_experimental/sla/client.py +66 -0
  4. prefect/_experimental/sla/objects.py +53 -0
  5. prefect/_internal/_logging.py +15 -3
  6. prefect/_internal/compatibility/async_dispatch.py +22 -16
  7. prefect/_internal/compatibility/deprecated.py +42 -18
  8. prefect/_internal/compatibility/migration.py +2 -2
  9. prefect/_internal/concurrency/inspection.py +12 -14
  10. prefect/_internal/concurrency/primitives.py +2 -2
  11. prefect/_internal/concurrency/services.py +154 -80
  12. prefect/_internal/concurrency/waiters.py +13 -9
  13. prefect/_internal/pydantic/annotations/pendulum.py +7 -7
  14. prefect/_internal/pytz.py +4 -3
  15. prefect/_internal/retries.py +10 -5
  16. prefect/_internal/schemas/bases.py +19 -10
  17. prefect/_internal/schemas/validators.py +227 -388
  18. prefect/_version.py +3 -3
  19. prefect/automations.py +236 -30
  20. prefect/blocks/__init__.py +3 -3
  21. prefect/blocks/abstract.py +53 -30
  22. prefect/blocks/core.py +183 -84
  23. prefect/blocks/notifications.py +133 -73
  24. prefect/blocks/redis.py +13 -9
  25. prefect/blocks/system.py +24 -11
  26. prefect/blocks/webhook.py +7 -5
  27. prefect/cache_policies.py +3 -2
  28. prefect/client/orchestration/__init__.py +1957 -0
  29. prefect/client/orchestration/_artifacts/__init__.py +0 -0
  30. prefect/client/orchestration/_artifacts/client.py +239 -0
  31. prefect/client/orchestration/_automations/__init__.py +0 -0
  32. prefect/client/orchestration/_automations/client.py +329 -0
  33. prefect/client/orchestration/_blocks_documents/__init__.py +0 -0
  34. prefect/client/orchestration/_blocks_documents/client.py +334 -0
  35. prefect/client/orchestration/_blocks_schemas/__init__.py +0 -0
  36. prefect/client/orchestration/_blocks_schemas/client.py +200 -0
  37. prefect/client/orchestration/_blocks_types/__init__.py +0 -0
  38. prefect/client/orchestration/_blocks_types/client.py +380 -0
  39. prefect/client/orchestration/_concurrency_limits/__init__.py +0 -0
  40. prefect/client/orchestration/_concurrency_limits/client.py +762 -0
  41. prefect/client/orchestration/_deployments/__init__.py +0 -0
  42. prefect/client/orchestration/_deployments/client.py +1128 -0
  43. prefect/client/orchestration/_flow_runs/__init__.py +0 -0
  44. prefect/client/orchestration/_flow_runs/client.py +903 -0
  45. prefect/client/orchestration/_flows/__init__.py +0 -0
  46. prefect/client/orchestration/_flows/client.py +343 -0
  47. prefect/client/orchestration/_logs/__init__.py +0 -0
  48. prefect/client/orchestration/_logs/client.py +97 -0
  49. prefect/client/orchestration/_variables/__init__.py +0 -0
  50. prefect/client/orchestration/_variables/client.py +157 -0
  51. prefect/client/orchestration/base.py +46 -0
  52. prefect/client/orchestration/routes.py +145 -0
  53. prefect/client/schemas/__init__.py +68 -28
  54. prefect/client/schemas/actions.py +2 -2
  55. prefect/client/schemas/filters.py +5 -0
  56. prefect/client/schemas/objects.py +8 -15
  57. prefect/client/schemas/schedules.py +22 -10
  58. prefect/concurrency/_asyncio.py +87 -0
  59. prefect/concurrency/{events.py → _events.py} +10 -10
  60. prefect/concurrency/asyncio.py +20 -104
  61. prefect/concurrency/context.py +6 -4
  62. prefect/concurrency/services.py +26 -74
  63. prefect/concurrency/sync.py +23 -44
  64. prefect/concurrency/v1/_asyncio.py +63 -0
  65. prefect/concurrency/v1/{events.py → _events.py} +13 -15
  66. prefect/concurrency/v1/asyncio.py +27 -80
  67. prefect/concurrency/v1/context.py +6 -4
  68. prefect/concurrency/v1/services.py +33 -79
  69. prefect/concurrency/v1/sync.py +18 -37
  70. prefect/context.py +66 -45
  71. prefect/deployments/base.py +10 -144
  72. prefect/deployments/flow_runs.py +12 -2
  73. prefect/deployments/runner.py +53 -4
  74. prefect/deployments/steps/pull.py +13 -0
  75. prefect/engine.py +17 -4
  76. prefect/events/clients.py +7 -1
  77. prefect/events/schemas/events.py +3 -2
  78. prefect/filesystems.py +6 -2
  79. prefect/flow_engine.py +101 -85
  80. prefect/flows.py +10 -1
  81. prefect/input/run_input.py +2 -1
  82. prefect/logging/logging.yml +1 -1
  83. prefect/main.py +1 -3
  84. prefect/results.py +2 -307
  85. prefect/runner/runner.py +4 -2
  86. prefect/runner/storage.py +87 -21
  87. prefect/serializers.py +32 -25
  88. prefect/settings/legacy.py +4 -4
  89. prefect/settings/models/api.py +3 -3
  90. prefect/settings/models/cli.py +3 -3
  91. prefect/settings/models/client.py +5 -3
  92. prefect/settings/models/cloud.py +8 -3
  93. prefect/settings/models/deployments.py +3 -3
  94. prefect/settings/models/experiments.py +4 -7
  95. prefect/settings/models/flows.py +3 -3
  96. prefect/settings/models/internal.py +4 -2
  97. prefect/settings/models/logging.py +4 -3
  98. prefect/settings/models/results.py +3 -3
  99. prefect/settings/models/root.py +3 -2
  100. prefect/settings/models/runner.py +4 -4
  101. prefect/settings/models/server/api.py +3 -3
  102. prefect/settings/models/server/database.py +11 -4
  103. prefect/settings/models/server/deployments.py +6 -2
  104. prefect/settings/models/server/ephemeral.py +4 -2
  105. prefect/settings/models/server/events.py +3 -2
  106. prefect/settings/models/server/flow_run_graph.py +6 -2
  107. prefect/settings/models/server/root.py +3 -3
  108. prefect/settings/models/server/services.py +26 -11
  109. prefect/settings/models/server/tasks.py +6 -3
  110. prefect/settings/models/server/ui.py +3 -3
  111. prefect/settings/models/tasks.py +5 -5
  112. prefect/settings/models/testing.py +3 -3
  113. prefect/settings/models/worker.py +5 -3
  114. prefect/settings/profiles.py +15 -2
  115. prefect/states.py +61 -45
  116. prefect/task_engine.py +54 -75
  117. prefect/task_runners.py +56 -55
  118. prefect/task_worker.py +2 -2
  119. prefect/tasks.py +90 -36
  120. prefect/telemetry/bootstrap.py +10 -9
  121. prefect/telemetry/run_telemetry.py +13 -8
  122. prefect/telemetry/services.py +4 -0
  123. prefect/transactions.py +4 -15
  124. prefect/utilities/_git.py +34 -0
  125. prefect/utilities/asyncutils.py +1 -1
  126. prefect/utilities/engine.py +3 -19
  127. prefect/utilities/generics.py +18 -0
  128. prefect/utilities/templating.py +25 -1
  129. prefect/workers/base.py +6 -3
  130. prefect/workers/process.py +1 -1
  131. {prefect_client-3.1.10.dist-info → prefect_client-3.1.12.dist-info}/METADATA +2 -2
  132. {prefect_client-3.1.10.dist-info → prefect_client-3.1.12.dist-info}/RECORD +135 -109
  133. prefect/client/orchestration.py +0 -4523
  134. prefect/records/__init__.py +0 -1
  135. prefect/records/base.py +0 -235
  136. prefect/records/filesystem.py +0 -213
  137. prefect/records/memory.py +0 -184
  138. prefect/records/result_store.py +0 -70
  139. {prefect_client-3.1.10.dist-info → prefect_client-3.1.12.dist-info}/LICENSE +0 -0
  140. {prefect_client-3.1.10.dist-info → prefect_client-3.1.12.dist-info}/WHEEL +0 -0
  141. {prefect_client-3.1.10.dist-info → prefect_client-3.1.12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,145 @@
1
+ from typing import Literal
2
+
3
+ ServerRoutes = Literal[
4
+ "/admin/database/clear",
5
+ "/admin/database/create",
6
+ "/admin/database/drop",
7
+ "/admin/settings",
8
+ "/admin/version",
9
+ "/artifacts/",
10
+ "/artifacts/{id}",
11
+ "/artifacts/{key}/latest",
12
+ "/artifacts/count",
13
+ "/artifacts/filter",
14
+ "/artifacts/latest/count",
15
+ "/artifacts/latest/filter",
16
+ "/automations/",
17
+ "/automations/{id}",
18
+ "/automations/count",
19
+ "/automations/filter",
20
+ "/automations/owned-by/{resource_id}",
21
+ "/automations/related-to/{resource_id}",
22
+ "/block_capabilities/",
23
+ "/block_documents/",
24
+ "/block_documents/{id}",
25
+ "/block_documents/count",
26
+ "/block_documents/filter",
27
+ "/block_schemas/",
28
+ "/block_schemas/{id}",
29
+ "/block_schemas/checksum/{checksum}",
30
+ "/block_schemas/filter",
31
+ "/block_types/",
32
+ "/block_types/{id}",
33
+ "/block_types/filter",
34
+ "/block_types/install_system_block_types",
35
+ "/block_types/slug/{slug}",
36
+ "/block_types/slug/{slug}/block_documents",
37
+ "/block_types/slug/{slug}/block_documents/name/{block_document_name}",
38
+ "/collections/views/{view}",
39
+ "/concurrency_limits/",
40
+ "/concurrency_limits/{id}",
41
+ "/concurrency_limits/decrement",
42
+ "/concurrency_limits/filter",
43
+ "/concurrency_limits/increment",
44
+ "/concurrency_limits/tag/{tag}",
45
+ "/concurrency_limits/tag/{tag}/reset",
46
+ "/csrf-token",
47
+ "/deployments/",
48
+ "/deployments/{id}",
49
+ "/deployments/{id}/create_flow_run",
50
+ "/deployments/{id}/pause_deployment",
51
+ "/deployments/{id}/resume_deployment",
52
+ "/deployments/{id}/schedule",
53
+ "/deployments/{id}/schedules",
54
+ "/deployments/{id}/schedules/{schedule_id}",
55
+ "/deployments/{id}/work_queue_check",
56
+ "/deployments/count",
57
+ "/deployments/filter",
58
+ "/deployments/get_scheduled_flow_runs",
59
+ "/deployments/name/{flow_name}/{deployment_name}",
60
+ "/deployments/paginate",
61
+ "/events",
62
+ "/events/count-by/{countable}",
63
+ "/events/filter",
64
+ "/events/filter/next",
65
+ "/flow_run_notification_policies/",
66
+ "/flow_run_notification_policies/{id}",
67
+ "/flow_run_notification_policies/filter",
68
+ "/flow_run_states/",
69
+ "/flow_run_states/{id}",
70
+ "/flow_runs/",
71
+ "/flow_runs/{id}",
72
+ "/flow_runs/{id}/graph",
73
+ "/flow_runs/{id}/graph-v2",
74
+ "/flow_runs/{id}/input",
75
+ "/flow_runs/{id}/input/{key}",
76
+ "/flow_runs/{id}/input/filter",
77
+ "/flow_runs/{id}/labels",
78
+ "/flow_runs/{id}/logs/download",
79
+ "/flow_runs/{id}/resume",
80
+ "/flow_runs/{id}/set_state",
81
+ "/flow_runs/count",
82
+ "/flow_runs/filter",
83
+ "/flow_runs/history",
84
+ "/flow_runs/lateness",
85
+ "/flow_runs/paginate",
86
+ "/flows/",
87
+ "/flows/{id}",
88
+ "/flows/count",
89
+ "/flows/filter",
90
+ "/flows/name/{name}",
91
+ "/flows/paginate",
92
+ "/health",
93
+ "/hello",
94
+ "/logs/",
95
+ "/logs/filter",
96
+ "/ready",
97
+ "/saved_searches/",
98
+ "/saved_searches/{id}",
99
+ "/saved_searches/filter",
100
+ "/task_run_states/",
101
+ "/task_run_states/{id}",
102
+ "/task_runs/",
103
+ "/task_runs/{id}",
104
+ "/task_runs/{id}/set_state",
105
+ "/task_runs/count",
106
+ "/task_runs/filter",
107
+ "/task_runs/history",
108
+ "/task_workers/filter",
109
+ "/templates/validate",
110
+ "/ui/flow_runs/count-task-runs",
111
+ "/ui/flow_runs/history",
112
+ "/ui/flows/count-deployments",
113
+ "/ui/flows/next-runs",
114
+ "/ui/schemas/validate",
115
+ "/ui/task_runs/count",
116
+ "/ui/task_runs/dashboard/counts",
117
+ "/v2/concurrency_limits/",
118
+ "/v2/concurrency_limits/{id_or_name}",
119
+ "/v2/concurrency_limits/decrement",
120
+ "/v2/concurrency_limits/filter",
121
+ "/v2/concurrency_limits/increment",
122
+ "/variables/",
123
+ "/variables/{id}",
124
+ "/variables/count",
125
+ "/variables/filter",
126
+ "/variables/name/{name}",
127
+ "/version",
128
+ "/work_pools/",
129
+ "/work_pools/{name}",
130
+ "/work_pools/{name}/get_scheduled_flow_runs",
131
+ "/work_pools/{work_pool_name}/queues",
132
+ "/work_pools/{work_pool_name}/queues/{name}",
133
+ "/work_pools/{work_pool_name}/queues/filter",
134
+ "/work_pools/{work_pool_name}/workers/{name}",
135
+ "/work_pools/{work_pool_name}/workers/filter",
136
+ "/work_pools/{work_pool_name}/workers/heartbeat",
137
+ "/work_pools/count",
138
+ "/work_pools/filter",
139
+ "/work_queues/",
140
+ "/work_queues/{id}",
141
+ "/work_queues/{id}/get_runs",
142
+ "/work_queues/{id}/status",
143
+ "/work_queues/filter",
144
+ "/work_queues/name/{name}",
145
+ ]
@@ -1,32 +1,58 @@
1
- # Some objects are exported here for backwards compatibility.
2
- # In general, it is recommended to import schemas from their respective modules.
1
+ import importlib
2
+ import sys
3
+ from typing import Any, TYPE_CHECKING
3
4
 
4
- from .actions import BlockTypeUpdate, StateCreate
5
- from .objects import (
6
- DEFAULT_BLOCK_SCHEMA_VERSION,
7
- BlockDocument,
8
- BlockSchema,
9
- BlockType,
10
- FlowRun,
11
- FlowRunPolicy,
12
- State,
13
- StateDetails,
14
- StateType,
15
- TaskRun,
16
- TaskRunInput,
17
- TaskRunPolicy,
18
- TaskRunResult,
19
- Workspace,
20
- )
21
- from .responses import (
22
- OrchestrationResult,
23
- SetStateStatus,
24
- StateAbortDetails,
25
- StateAcceptDetails,
26
- StateRejectDetails,
27
- )
5
+ if TYPE_CHECKING:
6
+ from .actions import BlockTypeUpdate, StateCreate
7
+ from .objects import (
8
+ DEFAULT_BLOCK_SCHEMA_VERSION,
9
+ BlockDocument,
10
+ BlockSchema,
11
+ BlockType,
12
+ FlowRun,
13
+ FlowRunPolicy,
14
+ State,
15
+ StateDetails,
16
+ StateType,
17
+ TaskRun,
18
+ TaskRunInput,
19
+ TaskRunPolicy,
20
+ TaskRunResult,
21
+ Workspace,
22
+ )
23
+ from .responses import (
24
+ OrchestrationResult,
25
+ SetStateStatus,
26
+ StateAbortDetails,
27
+ StateAcceptDetails,
28
+ StateRejectDetails,
29
+ )
28
30
 
29
- __all__ = (
31
+ _public_api = {
32
+ "BlockDocument": (__package__, ".objects"),
33
+ "BlockSchema": (__package__, ".objects"),
34
+ "BlockType": (__package__, ".objects"),
35
+ "BlockTypeUpdate": (__package__, ".actions"),
36
+ "DEFAULT_BLOCK_SCHEMA_VERSION": (__package__, ".objects"),
37
+ "FlowRun": (__package__, ".objects"),
38
+ "FlowRunPolicy": (__package__, ".objects"),
39
+ "OrchestrationResult": (__package__, ".responses"),
40
+ "SetStateStatus": (__package__, ".responses"),
41
+ "State": (__package__, ".objects"),
42
+ "StateAbortDetails": (__package__, ".responses"),
43
+ "StateAcceptDetails": (__package__, ".responses"),
44
+ "StateCreate": (__package__, ".actions"),
45
+ "StateDetails": (__package__, ".objects"),
46
+ "StateRejectDetails": (__package__, ".responses"),
47
+ "StateType": (__package__, ".objects"),
48
+ "TaskRun": (__package__, ".objects"),
49
+ "TaskRunInput": (__package__, ".objects"),
50
+ "TaskRunPolicy": (__package__, ".objects"),
51
+ "TaskRunResult": (__package__, ".objects"),
52
+ "Workspace": (__package__, ".objects"),
53
+ }
54
+
55
+ __all__ = [
30
56
  "BlockDocument",
31
57
  "BlockSchema",
32
58
  "BlockType",
@@ -48,4 +74,18 @@ __all__ = (
48
74
  "TaskRunPolicy",
49
75
  "TaskRunResult",
50
76
  "Workspace",
51
- )
77
+ ]
78
+
79
+
80
+ def __getattr__(attr_name: str) -> Any:
81
+ try:
82
+ if (dynamic_attr := _public_api.get(attr_name)) is None:
83
+ raise AttributeError(f"module {__name__} has no attribute {attr_name}")
84
+
85
+ package, mname = dynamic_attr
86
+ module = importlib.import_module(mname, package=package)
87
+ return getattr(module, attr_name)
88
+ except ModuleNotFoundError as ex:
89
+ mname, _, attr = (ex.name or "").rpartition(".")
90
+ ctx = {"name": mname, "obj": attr} if sys.version_info >= (3, 10) else {}
91
+ raise AttributeError(f"module {mname} has no attribute {attr}", **ctx) from ex
@@ -39,7 +39,7 @@ from prefect.utilities.collections import listrepr
39
39
  from prefect.utilities.pydantic import get_class_fields_only
40
40
 
41
41
  if TYPE_CHECKING:
42
- from prefect.results import BaseResult, ResultRecordMetadata
42
+ from prefect.results import ResultRecordMetadata
43
43
 
44
44
  R = TypeVar("R")
45
45
 
@@ -51,7 +51,7 @@ class StateCreate(ActionBaseModel):
51
51
  name: Optional[str] = Field(default=None)
52
52
  message: Optional[str] = Field(default=None, examples=["Run started"])
53
53
  state_details: StateDetails = Field(default_factory=StateDetails)
54
- data: Union["BaseResult[Any]", "ResultRecordMetadata", Any] = Field(
54
+ data: Union["ResultRecordMetadata", Any] = Field(
55
55
  default=None,
56
56
  )
57
57
 
@@ -505,6 +505,11 @@ class DeploymentFilterTags(PrefectBaseModel, OperatorMixin):
505
505
  " superset of the list"
506
506
  ),
507
507
  )
508
+ any_: Optional[list[str]] = Field(
509
+ default=None,
510
+ examples=[["tag-1", "tag-2"]],
511
+ description="A list of tags to include",
512
+ )
508
513
  is_null_: Optional[bool] = Field(
509
514
  default=None, description="If true, only include deployments without tags"
510
515
  )
@@ -10,7 +10,6 @@ from typing import (
10
10
  Generic,
11
11
  Optional,
12
12
  Union,
13
- cast,
14
13
  overload,
15
14
  )
16
15
  from uuid import UUID, uuid4
@@ -65,7 +64,7 @@ from prefect.utilities.pydantic import handle_secret_render
65
64
 
66
65
  if TYPE_CHECKING:
67
66
  from prefect.client.schemas.actions import StateCreate
68
- from prefect.results import BaseResult, ResultRecordMetadata
67
+ from prefect.results import ResultRecordMetadata
69
68
 
70
69
  DateTime = pendulum.DateTime
71
70
  else:
@@ -122,7 +121,7 @@ class WorkPoolStatus(AutoEnum):
122
121
  PAUSED = AutoEnum.auto()
123
122
 
124
123
  @property
125
- def display_name(self):
124
+ def display_name(self) -> str:
126
125
  return self.name.replace("_", " ").capitalize()
127
126
 
128
127
 
@@ -195,9 +194,7 @@ class StateDetails(PrefectBaseModel):
195
194
 
196
195
 
197
196
  def data_discriminator(x: Any) -> str:
198
- if isinstance(x, dict) and "type" in x and x["type"] != "unpersisted":
199
- return "BaseResult"
200
- elif isinstance(x, dict) and "storage_key" in x:
197
+ if isinstance(x, dict) and "storage_key" in x:
201
198
  return "ResultRecordMetadata"
202
199
  return "Any"
203
200
 
@@ -214,7 +211,6 @@ class State(ObjectBaseModel, Generic[R]):
214
211
  state_details: StateDetails = Field(default_factory=StateDetails)
215
212
  data: Annotated[
216
213
  Union[
217
- Annotated["BaseResult[R]", Tag("BaseResult")],
218
214
  Annotated["ResultRecordMetadata", Tag("ResultRecordMetadata")],
219
215
  Annotated[Any, Tag("Any")],
220
216
  ],
@@ -347,15 +343,12 @@ class State(ObjectBaseModel, Generic[R]):
347
343
  """
348
344
  from prefect.client.schemas.actions import StateCreate
349
345
  from prefect.results import (
350
- BaseResult,
351
346
  ResultRecord,
352
347
  should_persist_result,
353
348
  )
354
349
 
355
- if isinstance(self.data, BaseResult):
356
- data = cast(BaseResult[R], self.data)
357
- elif isinstance(self.data, ResultRecord) and should_persist_result():
358
- data = self.data.metadata
350
+ if isinstance(self.data, ResultRecord) and should_persist_result():
351
+ data = self.data.metadata # pyright: ignore[reportUnknownMemberType] unable to narrow ResultRecord type
359
352
  else:
360
353
  data = None
361
354
 
@@ -386,7 +379,7 @@ class State(ObjectBaseModel, Generic[R]):
386
379
 
387
380
  @model_validator(mode="after")
388
381
  def set_unpersisted_results_to_none(self) -> Self:
389
- if isinstance(self.data, dict) and self.data.get("type") == "unpersisted":
382
+ if isinstance(self.data, dict) and self.data.get("type") == "unpersisted": # pyright: ignore[reportUnknownMemberType] unable to narrow dict type
390
383
  self.data = None
391
384
  return self
392
385
 
@@ -531,7 +524,7 @@ class FlowRunPolicy(PrefectBaseModel):
531
524
  @classmethod
532
525
  def populate_deprecated_fields(cls, values: Any) -> Any:
533
526
  if isinstance(values, dict):
534
- return set_run_policy_deprecated_fields(values)
527
+ return set_run_policy_deprecated_fields(values) # pyright: ignore[reportUnknownVariableType, reportUnknownArgumentType] unable to narrow dict type
535
528
  return values
536
529
 
537
530
 
@@ -1262,7 +1255,7 @@ class BlockDocumentReference(ObjectBaseModel):
1262
1255
  @classmethod
1263
1256
  def validate_parent_and_ref_are_different(cls, values: Any) -> Any:
1264
1257
  if isinstance(values, dict):
1265
- return validate_parent_and_ref_diff(values)
1258
+ return validate_parent_and_ref_diff(values) # pyright: ignore[reportUnknownVariableType, reportUnknownArgumentType] unable to narrow dict type
1266
1259
  return values
1267
1260
 
1268
1261
 
@@ -33,6 +33,23 @@ MAX_ITERATIONS = 1000
33
33
  MAX_RRULE_LENGTH = 6500
34
34
 
35
35
 
36
+ def is_valid_timezone(v: str) -> bool:
37
+ """
38
+ Validate that the provided timezone is a valid IANA timezone.
39
+
40
+ Unfortunately this list is slightly different from the list of valid
41
+ timezones in pendulum that we use for cron and interval timezone validation.
42
+ """
43
+ from prefect._internal.pytz import HAS_PYTZ
44
+
45
+ if HAS_PYTZ:
46
+ import pytz
47
+ else:
48
+ from prefect._internal import pytz
49
+
50
+ return v in pytz.all_timezones_set
51
+
52
+
36
53
  class IntervalSchedule(PrefectBaseModel):
37
54
  """
38
55
  A schedule formed by adding `interval` increments to an `anchor_date`. If no
@@ -305,18 +322,13 @@ class RRuleSchedule(PrefectBaseModel):
305
322
  Unfortunately this list is slightly different from the list of valid
306
323
  timezones in pendulum that we use for cron and interval timezone validation.
307
324
  """
308
- from prefect._internal.pytz import HAS_PYTZ
325
+ if v is None:
326
+ return "UTC"
309
327
 
310
- if HAS_PYTZ:
311
- import pytz
312
- else:
313
- from prefect._internal import pytz
328
+ if is_valid_timezone(v):
329
+ return v
314
330
 
315
- if v and v not in pytz.all_timezones_set:
316
- raise ValueError(f'Invalid timezone: "{v}"')
317
- elif v is None:
318
- return "UTC"
319
- return v
331
+ raise ValueError(f'Invalid timezone: "{v}"')
320
332
 
321
333
 
322
334
  class NoSchedule(PrefectBaseModel):
@@ -0,0 +1,87 @@
1
+ import asyncio
2
+ from typing import Literal, Optional
3
+
4
+ import httpx
5
+
6
+ from prefect._internal.compatibility.deprecated import deprecated_parameter
7
+ from prefect.client.orchestration import get_client
8
+ from prefect.client.schemas.responses import MinimalConcurrencyLimitResponse
9
+ from prefect.logging.loggers import get_run_logger
10
+
11
+ from .services import ConcurrencySlotAcquisitionService
12
+
13
+
14
+ class ConcurrencySlotAcquisitionError(Exception):
15
+ """Raised when an unhandlable occurs while acquiring concurrency slots."""
16
+
17
+
18
+ class AcquireConcurrencySlotTimeoutError(TimeoutError):
19
+ """Raised when acquiring a concurrency slot times out."""
20
+
21
+
22
+ @deprecated_parameter(
23
+ name="create_if_missing",
24
+ start_date="Sep 2024",
25
+ end_date="Oct 2024",
26
+ when=lambda x: x is not None,
27
+ help="Limits must be explicitly created before acquiring concurrency slots; see `strict` if you want to enforce this behavior.",
28
+ )
29
+ async def aacquire_concurrency_slots(
30
+ names: list[str],
31
+ slots: int,
32
+ mode: Literal["concurrency", "rate_limit"] = "concurrency",
33
+ timeout_seconds: Optional[float] = None,
34
+ create_if_missing: Optional[bool] = None,
35
+ max_retries: Optional[int] = None,
36
+ strict: bool = False,
37
+ ) -> list[MinimalConcurrencyLimitResponse]:
38
+ service = ConcurrencySlotAcquisitionService.instance(frozenset(names))
39
+ future = service.send(
40
+ (slots, mode, timeout_seconds, create_if_missing, max_retries)
41
+ )
42
+ try:
43
+ response = await asyncio.wrap_future(future)
44
+ except TimeoutError as timeout:
45
+ raise AcquireConcurrencySlotTimeoutError(
46
+ f"Attempt to acquire concurrency slots timed out after {timeout_seconds} second(s)"
47
+ ) from timeout
48
+ except Exception as exc:
49
+ raise ConcurrencySlotAcquisitionError(
50
+ f"Unable to acquire concurrency slots on {names!r}"
51
+ ) from exc
52
+
53
+ retval = _response_to_minimal_concurrency_limit_response(response)
54
+
55
+ if not retval:
56
+ if strict:
57
+ raise ConcurrencySlotAcquisitionError(
58
+ f"Concurrency limits {names!r} must be created before acquiring slots"
59
+ )
60
+ try:
61
+ logger = get_run_logger()
62
+ except Exception:
63
+ pass
64
+ else:
65
+ logger.warning(
66
+ f"Concurrency limits {names!r} do not exist - skipping acquisition."
67
+ )
68
+
69
+ return retval
70
+
71
+
72
+ async def arelease_concurrency_slots(
73
+ names: list[str], slots: int, occupancy_seconds: float
74
+ ) -> list[MinimalConcurrencyLimitResponse]:
75
+ async with get_client() as client:
76
+ response = await client.release_concurrency_slots(
77
+ names=names, slots=slots, occupancy_seconds=occupancy_seconds
78
+ )
79
+ return _response_to_minimal_concurrency_limit_response(response)
80
+
81
+
82
+ def _response_to_minimal_concurrency_limit_response(
83
+ response: httpx.Response,
84
+ ) -> list[MinimalConcurrencyLimitResponse]:
85
+ return [
86
+ MinimalConcurrencyLimitResponse.model_validate(obj_) for obj_ in response.json()
87
+ ]
@@ -1,4 +1,4 @@
1
- from typing import Dict, List, Literal, Optional, Union
1
+ from typing import Literal, Optional, Union
2
2
  from uuid import UUID
3
3
 
4
4
  from prefect.client.schemas.responses import MinimalConcurrencyLimitResponse
@@ -8,11 +8,11 @@ from prefect.events import Event, RelatedResource, emit_event
8
8
  def _emit_concurrency_event(
9
9
  phase: Union[Literal["acquired"], Literal["released"]],
10
10
  primary_limit: MinimalConcurrencyLimitResponse,
11
- related_limits: List[MinimalConcurrencyLimitResponse],
11
+ related_limits: list[MinimalConcurrencyLimitResponse],
12
12
  slots: int,
13
13
  follows: Union[Event, None] = None,
14
14
  ) -> Union[Event, None]:
15
- resource: Dict[str, str] = {
15
+ resource: dict[str, str] = {
16
16
  "prefect.resource.id": f"prefect.concurrency-limit.{primary_limit.id}",
17
17
  "prefect.resource.name": primary_limit.name,
18
18
  "slots-acquired": str(slots),
@@ -38,11 +38,11 @@ def _emit_concurrency_event(
38
38
  )
39
39
 
40
40
 
41
- def _emit_concurrency_acquisition_events(
42
- limits: List[MinimalConcurrencyLimitResponse],
41
+ def emit_concurrency_acquisition_events(
42
+ limits: list[MinimalConcurrencyLimitResponse],
43
43
  occupy: int,
44
- ) -> Dict[UUID, Optional[Event]]:
45
- events = {}
44
+ ) -> dict[UUID, Optional[Event]]:
45
+ events: dict[UUID, Optional[Event]] = {}
46
46
  for limit in limits:
47
47
  event = _emit_concurrency_event("acquired", limit, limits, occupy)
48
48
  events[limit.id] = event
@@ -50,10 +50,10 @@ def _emit_concurrency_acquisition_events(
50
50
  return events
51
51
 
52
52
 
53
- def _emit_concurrency_release_events(
54
- limits: List[MinimalConcurrencyLimitResponse],
53
+ def emit_concurrency_release_events(
54
+ limits: list[MinimalConcurrencyLimitResponse],
55
55
  occupy: int,
56
- events: Dict[UUID, Optional[Event]],
56
+ events: dict[UUID, Optional[Event]],
57
57
  ) -> None:
58
58
  for limit in limits:
59
59
  _emit_concurrency_event("released", limit, limits, occupy, events[limit.id])