prefect-client 2.16.6__py3-none-any.whl → 2.16.7__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 (44) hide show
  1. prefect/_internal/pydantic/__init__.py +21 -1
  2. prefect/_internal/pydantic/_base_model.py +16 -0
  3. prefect/_internal/pydantic/_compat.py +325 -74
  4. prefect/_internal/pydantic/_flags.py +15 -0
  5. prefect/_internal/schemas/validators.py +582 -9
  6. prefect/artifacts.py +179 -70
  7. prefect/client/orchestration.py +1 -1
  8. prefect/client/schemas/actions.py +2 -2
  9. prefect/client/schemas/objects.py +13 -24
  10. prefect/client/schemas/schedules.py +18 -80
  11. prefect/deployments/deployments.py +22 -86
  12. prefect/deployments/runner.py +8 -11
  13. prefect/events/__init__.py +40 -1
  14. prefect/events/clients.py +17 -20
  15. prefect/events/filters.py +5 -6
  16. prefect/events/related.py +1 -1
  17. prefect/events/schemas/__init__.py +5 -0
  18. prefect/events/schemas/automations.py +303 -0
  19. prefect/events/{schemas.py → schemas/deployment_triggers.py} +146 -270
  20. prefect/events/schemas/events.py +285 -0
  21. prefect/events/schemas/labelling.py +106 -0
  22. prefect/events/utilities.py +2 -2
  23. prefect/events/worker.py +1 -1
  24. prefect/filesystems.py +8 -37
  25. prefect/flows.py +4 -4
  26. prefect/infrastructure/kubernetes.py +12 -56
  27. prefect/infrastructure/provisioners/__init__.py +1 -0
  28. prefect/pydantic/__init__.py +4 -0
  29. prefect/pydantic/main.py +15 -0
  30. prefect/runner/runner.py +2 -2
  31. prefect/runner/server.py +1 -1
  32. prefect/serializers.py +13 -61
  33. prefect/settings.py +34 -12
  34. prefect/task_server.py +21 -7
  35. prefect/utilities/asyncutils.py +1 -1
  36. prefect/utilities/context.py +33 -1
  37. prefect/workers/base.py +1 -2
  38. prefect/workers/block.py +3 -7
  39. {prefect_client-2.16.6.dist-info → prefect_client-2.16.7.dist-info}/METADATA +2 -2
  40. {prefect_client-2.16.6.dist-info → prefect_client-2.16.7.dist-info}/RECORD +43 -36
  41. prefect/utilities/validation.py +0 -63
  42. {prefect_client-2.16.6.dist-info → prefect_client-2.16.7.dist-info}/LICENSE +0 -0
  43. {prefect_client-2.16.6.dist-info → prefect_client-2.16.7.dist-info}/WHEEL +0 -0
  44. {prefect_client-2.16.6.dist-info → prefect_client-2.16.7.dist-info}/top_level.txt +0 -0
@@ -1,196 +1,122 @@
1
+ """
2
+ Schemas for defining triggers within a Prefect deployment YAML. This is a separate
3
+ parallel hierarchy for representing triggers so that they can also include the
4
+ information necessary to create an automation.
5
+
6
+ These triggers should follow the validation rules of the main Trigger class hierarchy as
7
+ closely as possible (because otherwise users will get validation errors creating
8
+ triggers), but we can be more liberal with the defaults here to make it simpler to
9
+ create them from YAML.
10
+ """
11
+
1
12
  import abc
13
+ import textwrap
2
14
  from datetime import timedelta
3
- from enum import Enum
4
15
  from typing import (
5
16
  Any,
6
17
  Dict,
7
- Iterable,
8
18
  List,
9
19
  Literal,
10
20
  Optional,
11
21
  Set,
12
- Tuple,
13
22
  Union,
14
- cast,
15
23
  )
16
- from uuid import UUID, uuid4
24
+ from uuid import UUID
17
25
 
18
- import pendulum
19
26
  from typing_extensions import TypeAlias
20
27
 
28
+ from prefect._internal.compatibility.deprecated import deprecated_class
21
29
  from prefect._internal.pydantic import HAS_PYDANTIC_V2
30
+ from prefect._internal.schemas.validators import validate_trigger_within
22
31
 
23
32
  if HAS_PYDANTIC_V2:
24
- from pydantic.v1 import Extra, Field, PrivateAttr, root_validator, validator
33
+ from pydantic.v1 import Field, PrivateAttr, root_validator, validator
25
34
  from pydantic.v1.fields import ModelField
26
35
  else:
27
- from pydantic import Extra, Field, PrivateAttr, root_validator, validator
36
+ from pydantic import Field, PrivateAttr, root_validator, validator
28
37
  from pydantic.fields import ModelField
29
38
 
30
39
  from prefect._internal.schemas.bases import PrefectBaseModel
31
- from prefect._internal.schemas.fields import DateTimeTZ
32
- from prefect.events.actions import ActionTypes, RunDeployment
33
- from prefect.utilities.collections import AutoEnum
34
-
35
- # These are defined by Prefect Cloud
36
- MAXIMUM_LABELS_PER_RESOURCE = 500
37
- MAXIMUM_RELATED_RESOURCES = 500
38
-
39
-
40
- class Posture(AutoEnum):
41
- Reactive = "Reactive"
42
- Proactive = "Proactive"
43
- Metric = "Metric"
44
-
45
-
46
- class ResourceSpecification(PrefectBaseModel):
47
- __root__: Dict[str, Union[str, List[str]]]
48
-
49
-
50
- class Labelled(PrefectBaseModel):
51
- """An object defined by string labels and values"""
52
-
53
- __root__: Dict[str, str]
54
-
55
- def keys(self) -> Iterable[str]:
56
- return self.__root__.keys()
57
-
58
- def items(self) -> Iterable[Tuple[str, str]]:
59
- return self.__root__.items()
60
-
61
- def __getitem__(self, label: str) -> str:
62
- return self.__root__[label]
63
-
64
- def __setitem__(self, label: str, value: str) -> str:
65
- self.__root__[label] = value
66
- return value
67
-
68
-
69
- class Resource(Labelled):
70
- """An observable business object of interest to the user"""
71
-
72
- @root_validator(pre=True)
73
- def enforce_maximum_labels(cls, values: Dict[str, Any]):
74
- labels = values.get("__root__")
75
- if not isinstance(labels, dict):
76
- return values
77
-
78
- if len(labels) > MAXIMUM_LABELS_PER_RESOURCE:
79
- raise ValueError(
80
- "The maximum number of labels per resource "
81
- f"is {MAXIMUM_LABELS_PER_RESOURCE}"
82
- )
83
-
84
- return values
85
-
86
- @root_validator(pre=True)
87
- def requires_resource_id(cls, values: Dict[str, Any]):
88
- labels = values.get("__root__")
89
- if not isinstance(labels, dict):
90
- return values
91
-
92
- labels = cast(Dict[str, str], labels)
93
-
94
- if "prefect.resource.id" not in labels:
95
- raise ValueError("Resources must include the prefect.resource.id label")
96
- if not labels["prefect.resource.id"]:
97
- raise ValueError("The prefect.resource.id label must be non-empty")
98
-
99
- return values
100
-
101
- @property
102
- def id(self) -> str:
103
- return self["prefect.resource.id"]
104
-
105
-
106
- class RelatedResource(Resource):
107
- """A Resource with a specific role in an Event"""
40
+ from prefect.events.actions import RunDeployment
41
+
42
+ from .automations import (
43
+ Automation,
44
+ CompoundTrigger,
45
+ EventTrigger,
46
+ MetricTrigger,
47
+ MetricTriggerQuery,
48
+ Posture,
49
+ SequenceTrigger,
50
+ Trigger,
51
+ TriggerTypes,
52
+ )
53
+ from .events import ResourceSpecification
108
54
 
109
- @root_validator(pre=True)
110
- def requires_resource_role(cls, values: Dict[str, Any]):
111
- labels = values.get("__root__")
112
- if not isinstance(labels, dict):
113
- return values
114
55
 
115
- labels = cast(Dict[str, str], labels)
56
+ class BaseDeploymentTrigger(PrefectBaseModel, abc.ABC, extra="ignore"):
57
+ """
58
+ Base class describing a set of criteria that must be satisfied in order to trigger
59
+ an automation.
60
+ """
116
61
 
117
- if "prefect.resource.role" not in labels:
118
- raise ValueError(
119
- "Related Resources must include the prefect.resource.role label"
120
- )
121
- if not labels["prefect.resource.role"]:
122
- raise ValueError("The prefect.resource.role label must be non-empty")
62
+ # Fields from Automation
123
63
 
124
- return values
64
+ name: Optional[str] = Field(
65
+ None, description="The name to give to the automation created for this trigger."
66
+ )
67
+ description: str = Field("", description="A longer description of this automation")
68
+ enabled: bool = Field(True, description="Whether this automation will be evaluated")
125
69
 
126
- @property
127
- def role(self) -> str:
128
- return self["prefect.resource.role"]
70
+ # Fields from Trigger
129
71
 
72
+ type: str
130
73
 
131
- class Event(PrefectBaseModel):
132
- """The client-side view of an event that has happened to a Resource"""
74
+ # Fields from Deployment
133
75
 
134
- occurred: DateTimeTZ = Field(
135
- default_factory=pendulum.now,
136
- description="When the event happened from the sender's perspective",
137
- )
138
- event: str = Field(
139
- description="The name of the event that happened",
140
- )
141
- resource: Resource = Field(
142
- description="The primary Resource this event concerns",
143
- )
144
- related: List[RelatedResource] = Field(
145
- default_factory=list,
146
- description="A list of additional Resources involved in this event",
147
- )
148
- payload: Dict[str, Any] = Field(
149
- default_factory=dict,
150
- description="An open-ended set of data describing what happened",
151
- )
152
- id: UUID = Field(
153
- default_factory=uuid4,
154
- description="The client-provided identifier of this event",
155
- )
156
- follows: Optional[UUID] = Field(
76
+ parameters: Optional[Dict[str, Any]] = Field(
157
77
  None,
158
78
  description=(
159
- "The ID of an event that is known to have occurred prior to this "
160
- "one. If set, this may be used to establish a more precise "
161
- "ordering of causally-related events when they occur close enough "
162
- "together in time that the system may receive them out-of-order."
79
+ "The parameters to pass to the deployment, or None to use the "
80
+ "deployment's default parameters"
163
81
  ),
164
82
  )
165
83
 
166
- @property
167
- def involved_resources(self) -> Iterable[Resource]:
168
- return [self.resource] + list(self.related)
84
+ _deployment_id: Optional[UUID] = PrivateAttr(default=None)
169
85
 
170
- @validator("related")
171
- def enforce_maximum_related_resources(cls, value: List[RelatedResource]):
172
- if len(value) > MAXIMUM_RELATED_RESOURCES:
173
- raise ValueError(
174
- "The maximum number of related resources "
175
- f"is {MAXIMUM_RELATED_RESOURCES}"
176
- )
86
+ def set_deployment_id(self, deployment_id: UUID):
87
+ self._deployment_id = deployment_id
177
88
 
178
- return value
89
+ def owner_resource(self) -> Optional[str]:
90
+ return f"prefect.deployment.{self._deployment_id}"
179
91
 
92
+ def actions(self) -> List[RunDeployment]:
93
+ assert self._deployment_id
94
+ return [
95
+ RunDeployment(
96
+ parameters=self.parameters,
97
+ deployment_id=self._deployment_id,
98
+ )
99
+ ]
180
100
 
181
- class Trigger(PrefectBaseModel, abc.ABC):
182
- """
183
- Base class describing a set of criteria that must be satisfied in order to trigger
184
- an automation.
185
- """
101
+ def as_automation(self) -> Automation:
102
+ if not self.name:
103
+ raise ValueError("name is required")
186
104
 
187
- class Config:
188
- extra = Extra.ignore
105
+ return Automation(
106
+ name=self.name,
107
+ description=self.description,
108
+ enabled=self.enabled,
109
+ trigger=self.as_trigger(),
110
+ actions=self.actions(),
111
+ owner_resource=self.owner_resource(),
112
+ )
189
113
 
190
- type: str
114
+ @abc.abstractmethod
115
+ def as_trigger(self) -> Trigger:
116
+ ...
191
117
 
192
118
 
193
- class ResourceTrigger(Trigger, abc.ABC):
119
+ class DeploymentResourceTrigger(BaseDeploymentTrigger, abc.ABC):
194
120
  """
195
121
  Base class for triggers that may filter by the labels of resources.
196
122
  """
@@ -207,7 +133,7 @@ class ResourceTrigger(Trigger, abc.ABC):
207
133
  )
208
134
 
209
135
 
210
- class EventTrigger(ResourceTrigger):
136
+ class DeploymentEventTrigger(DeploymentResourceTrigger):
211
137
  """
212
138
  A trigger that fires based on the presence or absence of events within a given
213
139
  period of time.
@@ -245,7 +171,7 @@ class EventTrigger(ResourceTrigger):
245
171
  ),
246
172
  )
247
173
  posture: Literal[Posture.Reactive, Posture.Proactive] = Field( # type: ignore[valid-type]
248
- ...,
174
+ Posture.Reactive,
249
175
  description=(
250
176
  "The posture of this trigger, either Reactive or Proactive. Reactive "
251
177
  "triggers respond to the _presence_ of the expected events, while "
@@ -275,10 +201,7 @@ class EventTrigger(ResourceTrigger):
275
201
  def enforce_minimum_within(
276
202
  cls, value: timedelta, values, config, field: ModelField
277
203
  ):
278
- minimum = field.field_info.extra["minimum"]
279
- if value.total_seconds() < minimum:
280
- raise ValueError("The minimum within is 0 seconds")
281
- return value
204
+ return validate_trigger_within(value, field)
282
205
 
283
206
  @root_validator(skip_on_failure=True)
284
207
  def enforce_minimum_within_for_proactive_triggers(cls, values: Dict[str, Any]):
@@ -295,66 +218,20 @@ class EventTrigger(ResourceTrigger):
295
218
 
296
219
  return values
297
220
 
298
-
299
- class MetricTriggerOperator(Enum):
300
- LT = "<"
301
- LTE = "<="
302
- GT = ">"
303
- GTE = ">="
304
-
305
-
306
- class PrefectMetric(Enum):
307
- lateness = "lateness"
308
- duration = "duration"
309
- successes = "successes"
310
-
311
-
312
- class MetricTriggerQuery(PrefectBaseModel):
313
- """Defines a subset of the Trigger subclass, which is specific
314
- to Metric automations, that specify the query configurations
315
- and breaching conditions for the Automation"""
316
-
317
- name: PrefectMetric = Field(
318
- ...,
319
- description="The name of the metric to query.",
320
- )
321
- threshold: float = Field(
322
- ...,
323
- description=(
324
- "The threshold value against which we'll compare " "the query result."
325
- ),
326
- )
327
- operator: MetricTriggerOperator = Field(
328
- ...,
329
- description=(
330
- "The comparative operator (LT / LTE / GT / GTE) used to compare "
331
- "the query result against the threshold value."
332
- ),
333
- )
334
- range: timedelta = Field(
335
- timedelta(seconds=300), # defaults to 5 minutes
336
- minimum=300.0,
337
- exclusiveMinimum=False,
338
- description=(
339
- "The lookback duration (seconds) for a metric query. This duration is "
340
- "used to determine the time range over which the query will be executed. "
341
- "The minimum value is 300 seconds (5 minutes)."
342
- ),
343
- )
344
- firing_for: timedelta = Field(
345
- timedelta(seconds=300), # defaults to 5 minutes
346
- minimum=300.0,
347
- exclusiveMinimum=False,
348
- description=(
349
- "The duration (seconds) for which the metric query must breach "
350
- "or resolve continuously before the state is updated and the "
351
- "automation is triggered. "
352
- "The minimum value is 300 seconds (5 minutes)."
353
- ),
354
- )
221
+ def as_trigger(self) -> Trigger:
222
+ return EventTrigger(
223
+ match=self.match,
224
+ match_related=self.match_related,
225
+ after=self.after,
226
+ expect=self.expect,
227
+ for_each=self.for_each,
228
+ posture=self.posture,
229
+ threshold=self.threshold,
230
+ within=self.within,
231
+ )
355
232
 
356
233
 
357
- class MetricTrigger(ResourceTrigger):
234
+ class DeploymentMetricTrigger(DeploymentResourceTrigger):
358
235
  """
359
236
  A trigger that fires based on the results of a metric query.
360
237
  """
@@ -371,8 +248,16 @@ class MetricTrigger(ResourceTrigger):
371
248
  description="The metric query to evaluate for this trigger. ",
372
249
  )
373
250
 
251
+ def as_trigger(self) -> Trigger:
252
+ return MetricTrigger(
253
+ match=self.match,
254
+ match_related=self.match_related,
255
+ posture=self.posture,
256
+ metric=self.metric,
257
+ )
258
+
374
259
 
375
- class CompositeTrigger(Trigger, abc.ABC):
260
+ class DeploymentCompositeTrigger(BaseDeploymentTrigger, abc.ABC):
376
261
  """
377
262
  Requires some number of triggers to have fired within the given time period.
378
263
  """
@@ -382,7 +267,7 @@ class CompositeTrigger(Trigger, abc.ABC):
382
267
  within: Optional[timedelta]
383
268
 
384
269
 
385
- class CompoundTrigger(CompositeTrigger):
270
+ class DeploymentCompoundTrigger(DeploymentCompositeTrigger):
386
271
  """A composite trigger that requires some number of triggers to have
387
272
  fired within the given time period"""
388
273
 
@@ -403,57 +288,56 @@ class CompoundTrigger(CompositeTrigger):
403
288
 
404
289
  return values
405
290
 
291
+ def as_trigger(self) -> Trigger:
292
+ return CompoundTrigger(
293
+ require=self.require,
294
+ triggers=self.triggers,
295
+ within=self.within,
296
+ )
297
+
406
298
 
407
- class SequenceTrigger(CompositeTrigger):
299
+ class DeploymentSequenceTrigger(DeploymentCompositeTrigger):
408
300
  """A composite trigger that requires some number of triggers to have fired
409
301
  within the given time period in a specific order"""
410
302
 
411
303
  type: Literal["sequence"] = "sequence"
412
304
 
305
+ def as_trigger(self) -> Trigger:
306
+ return SequenceTrigger(
307
+ triggers=self.triggers,
308
+ within=self.within,
309
+ )
413
310
 
414
- TriggerTypes: TypeAlias = Union[
415
- EventTrigger, MetricTrigger, CompoundTrigger, SequenceTrigger
416
- ]
417
- """The union of all concrete trigger types that a user may actually create"""
418
-
419
- CompoundTrigger.update_forward_refs()
420
- SequenceTrigger.update_forward_refs()
421
-
422
-
423
- class Automation(PrefectBaseModel):
424
- """Defines an action a user wants to take when a certain number of events
425
- do or don't happen to the matching resources"""
426
311
 
427
- class Config:
428
- extra = Extra.ignore
312
+ # Concrete deployment trigger types
313
+ DeploymentTriggerTypes: TypeAlias = Union[
314
+ DeploymentEventTrigger,
315
+ DeploymentMetricTrigger,
316
+ DeploymentCompoundTrigger,
317
+ DeploymentSequenceTrigger,
318
+ ]
429
319
 
430
- name: str = Field(..., description="The name of this automation")
431
- description: str = Field("", description="A longer description of this automation")
432
320
 
433
- enabled: bool = Field(True, description="Whether this automation will be evaluated")
321
+ # The deprecated all-in-one DeploymentTrigger
434
322
 
435
- trigger: TriggerTypes = Field(
436
- ...,
437
- description=(
438
- "The criteria for which events this Automation covers and how it will "
439
- "respond to the presence or absence of those events"
440
- ),
441
- )
442
323
 
443
- actions: List[ActionTypes] = Field(
444
- ...,
445
- description="The actions to perform when this Automation triggers",
446
- )
447
- owner_resource: Optional[str] = Field(
448
- default=None, description="The owning resource of this automation"
449
- )
450
-
451
-
452
- class ExistingAutomation(Automation):
453
- id: UUID = Field(..., description="The ID of this automation")
324
+ @deprecated_class(
325
+ start_date="Mar 2024",
326
+ help=textwrap.dedent(
327
+ """
328
+ To associate a specific kind of trigger with a Deployment, use one of the more
329
+ specific deployment trigger types instead:
454
330
 
331
+ * DeploymentEventTrigger to associate an EventTrigger with a deployment
332
+ * DeploymentMetricTrigger to associate an MetricTrigger with a deployment
333
+ * DeploymentCompoundTrigger to associate an CompoundTrigger with a deployment
334
+ * DeploymentSequenceTrigger to associate an SequenceTrigger with a deployment
455
335
 
456
- class AutomationCreateFromTrigger(PrefectBaseModel):
336
+ In other cases, use the Automation and Trigger class hierarchy directly.
337
+ """
338
+ ),
339
+ )
340
+ class DeploymentTrigger(PrefectBaseModel):
457
341
  name: Optional[str] = Field(
458
342
  None, description="The name to give to the automation created for this trigger."
459
343
  )
@@ -540,6 +424,15 @@ class AutomationCreateFromTrigger(PrefectBaseModel):
540
424
  description="The metric query to evaluate for this trigger. ",
541
425
  )
542
426
 
427
+ _deployment_id: Optional[UUID] = PrivateAttr(default=None)
428
+ parameters: Optional[Dict[str, Any]] = Field(
429
+ None,
430
+ description=(
431
+ "The parameters to pass to the deployment, or None to use the "
432
+ "deployment's default parameters"
433
+ ),
434
+ )
435
+
543
436
  def as_automation(self) -> Automation:
544
437
  assert self.name
545
438
 
@@ -572,23 +465,6 @@ class AutomationCreateFromTrigger(PrefectBaseModel):
572
465
  owner_resource=self.owner_resource(),
573
466
  )
574
467
 
575
- def owner_resource(self) -> Optional[str]:
576
- return None
577
-
578
- def actions(self) -> List[ActionTypes]:
579
- raise NotImplementedError
580
-
581
-
582
- class DeploymentTrigger(AutomationCreateFromTrigger):
583
- _deployment_id: Optional[UUID] = PrivateAttr(default=None)
584
- parameters: Optional[Dict[str, Any]] = Field(
585
- None,
586
- description=(
587
- "The parameters to pass to the deployment, or None to use the "
588
- "deployment's default parameters"
589
- ),
590
- )
591
-
592
468
  def set_deployment_id(self, deployment_id: UUID):
593
469
  self._deployment_id = deployment_id
594
470