prefect-client 2.18.0__py3-none-any.whl → 2.18.2__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 (49) hide show
  1. prefect/_internal/schemas/fields.py +31 -12
  2. prefect/automations.py +162 -0
  3. prefect/blocks/core.py +1 -1
  4. prefect/blocks/notifications.py +2 -2
  5. prefect/blocks/system.py +2 -3
  6. prefect/client/orchestration.py +309 -30
  7. prefect/client/schemas/objects.py +11 -8
  8. prefect/client/schemas/sorting.py +9 -0
  9. prefect/client/utilities.py +25 -3
  10. prefect/concurrency/asyncio.py +11 -5
  11. prefect/concurrency/events.py +3 -3
  12. prefect/concurrency/services.py +1 -1
  13. prefect/concurrency/sync.py +9 -5
  14. prefect/deployments/deployments.py +27 -18
  15. prefect/deployments/runner.py +34 -26
  16. prefect/engine.py +3 -1
  17. prefect/events/actions.py +2 -1
  18. prefect/events/cli/automations.py +207 -46
  19. prefect/events/clients.py +53 -20
  20. prefect/events/filters.py +31 -4
  21. prefect/events/instrument.py +40 -40
  22. prefect/events/related.py +2 -1
  23. prefect/events/schemas/automations.py +52 -7
  24. prefect/events/schemas/deployment_triggers.py +16 -228
  25. prefect/events/schemas/events.py +18 -11
  26. prefect/events/schemas/labelling.py +1 -1
  27. prefect/events/utilities.py +1 -1
  28. prefect/events/worker.py +10 -7
  29. prefect/flows.py +42 -24
  30. prefect/input/actions.py +9 -9
  31. prefect/input/run_input.py +51 -37
  32. prefect/new_flow_engine.py +444 -0
  33. prefect/new_task_engine.py +488 -0
  34. prefect/results.py +3 -2
  35. prefect/runner/runner.py +3 -2
  36. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +45 -4
  37. prefect/settings.py +47 -0
  38. prefect/states.py +25 -19
  39. prefect/tasks.py +146 -19
  40. prefect/utilities/asyncutils.py +41 -0
  41. prefect/utilities/engine.py +6 -4
  42. prefect/utilities/schema_tools/validation.py +1 -1
  43. prefect/workers/process.py +2 -1
  44. {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/METADATA +1 -1
  45. {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/RECORD +48 -46
  46. prefect/concurrency/common.py +0 -0
  47. {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/LICENSE +0 -0
  48. {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/WHEEL +0 -0
  49. {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/top_level.txt +0 -0
@@ -3,6 +3,7 @@ from typing import Optional
3
3
  from uuid import UUID
4
4
 
5
5
  import pendulum
6
+ from typing_extensions import TypeAlias
6
7
 
7
8
  from prefect._internal.pydantic import HAS_PYDANTIC_V2
8
9
 
@@ -12,19 +13,37 @@ else:
12
13
  from pydantic import BaseModel, Field
13
14
 
14
15
 
15
- class DateTimeTZ(pendulum.DateTime):
16
- @classmethod
17
- def __get_validators__(cls):
18
- yield cls.validate
16
+ # Rather than subclassing pendulum.DateTime to add our pydantic-specific validation,
17
+ # which will lead to a lot of funky typing issues, we'll just monkeypatch the pydantic
18
+ # validators onto the class. Retaining this type alias means that we can still use it
19
+ # as we have been in class definitions, also guaranteeing that we'll be applying these
20
+ # validators by importing this module.
19
21
 
20
- @classmethod
21
- def validate(cls, v):
22
- if isinstance(v, str):
23
- return pendulum.parse(v)
24
- elif isinstance(v, datetime.datetime):
25
- return pendulum.instance(v)
26
- else:
27
- raise ValueError("Unrecognized datetime.")
22
+ DateTimeTZ: TypeAlias = pendulum.DateTime
23
+
24
+
25
+ def _datetime_patched_classmethod(function):
26
+ if hasattr(DateTimeTZ, function.__name__):
27
+ return function
28
+ setattr(DateTimeTZ, function.__name__, classmethod(function))
29
+ return function
30
+
31
+
32
+ @_datetime_patched_classmethod
33
+ def __get_validators__(cls):
34
+ yield getattr(cls, "validate")
35
+
36
+
37
+ @_datetime_patched_classmethod
38
+ def validate(cls, v) -> pendulum.DateTime:
39
+ if isinstance(v, str):
40
+ parsed = pendulum.parse(v)
41
+ assert isinstance(parsed, pendulum.DateTime)
42
+ return parsed
43
+ elif isinstance(v, datetime.datetime):
44
+ return pendulum.instance(v)
45
+ else:
46
+ raise ValueError("Unrecognized datetime.")
28
47
 
29
48
 
30
49
  class CreatedBy(BaseModel):
prefect/automations.py ADDED
@@ -0,0 +1,162 @@
1
+ from typing import Optional
2
+ from uuid import UUID
3
+
4
+ from pydantic import Field
5
+ from typing_extensions import Self
6
+
7
+ from prefect.client.utilities import get_or_create_client
8
+ from prefect.events.schemas.automations import (
9
+ AutomationCore,
10
+ CompositeTrigger,
11
+ CompoundTrigger,
12
+ EventTrigger,
13
+ MetricTrigger,
14
+ MetricTriggerOperator,
15
+ MetricTriggerQuery,
16
+ Posture,
17
+ PrefectMetric,
18
+ ResourceSpecification,
19
+ ResourceTrigger,
20
+ SequenceTrigger,
21
+ Trigger,
22
+ )
23
+ from prefect.exceptions import PrefectHTTPStatusError
24
+ from prefect.utilities.asyncutils import sync_compatible
25
+
26
+ __all__ = [
27
+ "AutomationCore",
28
+ "EventTrigger",
29
+ "ResourceTrigger",
30
+ "Posture",
31
+ "Trigger",
32
+ "ResourceSpecification",
33
+ "MetricTriggerOperator",
34
+ "MetricTrigger",
35
+ "PrefectMetric",
36
+ "CompositeTrigger",
37
+ "SequenceTrigger",
38
+ "CompoundTrigger",
39
+ "MetricTriggerQuery",
40
+ ]
41
+
42
+
43
+ class Automation(AutomationCore):
44
+ id: Optional[UUID] = Field(default=None, description="The ID of this automation")
45
+
46
+ @sync_compatible
47
+ async def create(self: Self) -> Self:
48
+ """
49
+ Create a new automation.
50
+
51
+ auto_to_create = Automation(
52
+ name="woodchonk",
53
+ trigger=EventTrigger(
54
+ expect={"animal.walked"},
55
+ match={
56
+ "genus": "Marmota",
57
+ "species": "monax",
58
+ },
59
+ posture="Reactive",
60
+ threshold=3,
61
+ within=timedelta(seconds=10),
62
+ ),
63
+ actions=[CancelFlowRun()]
64
+ )
65
+ created_automation = auto_to_create.create()
66
+ """
67
+ client, _ = get_or_create_client()
68
+ automation = AutomationCore(**self.dict(exclude={"id"}))
69
+ self.id = await client.create_automation(automation=automation)
70
+ return self
71
+
72
+ @sync_compatible
73
+ async def update(self: Self):
74
+ """
75
+ Updates an existing automation.
76
+ auto = Automation.read(id=123)
77
+ auto.name = "new name"
78
+ auto.update()
79
+ """
80
+
81
+ client, _ = get_or_create_client()
82
+ automation = AutomationCore(**self.dict(exclude={"id", "owner_resource"}))
83
+ await client.update_automation(automation_id=self.id, automation=automation)
84
+
85
+ @classmethod
86
+ @sync_compatible
87
+ async def read(
88
+ cls: Self, id: Optional[UUID] = None, name: Optional[str] = None
89
+ ) -> Self:
90
+ """
91
+ Read an automation by ID or name.
92
+ automation = Automation.read(name="woodchonk")
93
+
94
+ or
95
+
96
+ automation = Automation.read(id=UUID("b3514963-02b1-47a5-93d1-6eeb131041cb"))
97
+ """
98
+ if id and name:
99
+ raise ValueError("Only one of id or name can be provided")
100
+ if not id and not name:
101
+ raise ValueError("One of id or name must be provided")
102
+ client, _ = get_or_create_client()
103
+ if id:
104
+ try:
105
+ automation = await client.read_automation(automation_id=id)
106
+ except PrefectHTTPStatusError as exc:
107
+ if exc.response.status_code == 404:
108
+ raise ValueError(f"Automation with ID {id!r} not found")
109
+ return Automation(**automation.dict())
110
+ else:
111
+ automation = await client.read_automations_by_name(name=name)
112
+ if len(automation) > 0:
113
+ return Automation(**automation[0].dict()) if automation else None
114
+ else:
115
+ raise ValueError(f"Automation with name {name!r} not found")
116
+
117
+ @sync_compatible
118
+ async def delete(self: Self) -> bool:
119
+ """
120
+ auto = Automation.read(id = 123)
121
+ auto.delete()
122
+ """
123
+ try:
124
+ client, _ = get_or_create_client()
125
+ await client.delete_automation(self.id)
126
+ return True
127
+ except PrefectHTTPStatusError as exc:
128
+ if exc.response.status_code == 404:
129
+ return False
130
+ raise
131
+
132
+ @sync_compatible
133
+ async def disable(self: Self) -> bool:
134
+ """
135
+ Disable an automation.
136
+ auto = Automation.read(id = 123)
137
+ auto.disable()
138
+ """
139
+ try:
140
+ client, _ = get_or_create_client()
141
+ await client.pause_automation(self.id)
142
+ return True
143
+ except PrefectHTTPStatusError as exc:
144
+ if exc.response.status_code == 404:
145
+ return False
146
+ raise
147
+
148
+ @sync_compatible
149
+ async def enable(self: Self) -> bool:
150
+ """
151
+ Enable an automation.
152
+ auto = Automation.read(id = 123)
153
+ auto.enable()
154
+ """
155
+ try:
156
+ client, _ = get_or_create_client()
157
+ await client.resume_automation("asd")
158
+ return True
159
+ except PrefectHTTPStatusError as exc:
160
+ if exc.response.status_code == 404:
161
+ return False
162
+ raise
prefect/blocks/core.py CHANGED
@@ -996,7 +996,7 @@ class Block(BaseModel, ABC):
996
996
  return self._block_document_id
997
997
 
998
998
  @sync_compatible
999
- @instrument_instance_method_call()
999
+ @instrument_instance_method_call
1000
1000
  async def save(
1001
1001
  self,
1002
1002
  name: Optional[str] = None,
@@ -62,7 +62,7 @@ class AbstractAppriseNotificationBlock(NotificationBlock, ABC):
62
62
  self._start_apprise_client(self.url)
63
63
 
64
64
  @sync_compatible
65
- @instrument_instance_method_call()
65
+ @instrument_instance_method_call
66
66
  async def notify(
67
67
  self,
68
68
  body: str,
@@ -717,7 +717,7 @@ class CustomWebhookNotificationBlock(NotificationBlock):
717
717
  raise KeyError(f"{name}/{placeholder}")
718
718
 
719
719
  @sync_compatible
720
- @instrument_instance_method_call()
720
+ @instrument_instance_method_call
721
721
  async def notify(self, body: str, subject: Optional[str] = None):
722
722
  import httpx
723
723
 
prefect/blocks/system.py CHANGED
@@ -1,7 +1,5 @@
1
1
  from typing import Any
2
2
 
3
- import pendulum
4
-
5
3
  from prefect._internal.pydantic import HAS_PYDANTIC_V2
6
4
 
7
5
  if HAS_PYDANTIC_V2:
@@ -9,6 +7,7 @@ if HAS_PYDANTIC_V2:
9
7
  else:
10
8
  from pydantic import Field, SecretStr
11
9
 
10
+ from prefect._internal.schemas.fields import DateTimeTZ
12
11
  from prefect.blocks.core import Block
13
12
 
14
13
 
@@ -76,7 +75,7 @@ class DateTime(Block):
76
75
  _logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/8b3da9a6621e92108b8e6a75b82e15374e170ff7-48x48.png"
77
76
  _documentation_url = "https://docs.prefect.io/api-ref/prefect/blocks/system/#prefect.blocks.system.DateTime"
78
77
 
79
- value: pendulum.DateTime = Field(
78
+ value: DateTimeTZ = Field(
80
79
  default=...,
81
80
  description="An ISO 8601-compatible datetime value.",
82
81
  )