prefect-client 2.17.1__py3-none-any.whl → 2.18.1__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/_internal/compatibility/deprecated.py +2 -0
- prefect/_internal/pydantic/_compat.py +1 -0
- prefect/_internal/pydantic/utilities/field_validator.py +25 -10
- prefect/_internal/pydantic/utilities/model_dump.py +1 -1
- prefect/_internal/pydantic/utilities/model_validate.py +1 -1
- prefect/_internal/pydantic/utilities/model_validator.py +11 -3
- prefect/_internal/schemas/fields.py +31 -12
- prefect/_internal/schemas/validators.py +0 -6
- prefect/_version.py +97 -38
- prefect/blocks/abstract.py +34 -1
- prefect/blocks/core.py +1 -1
- prefect/blocks/notifications.py +16 -7
- prefect/blocks/system.py +2 -3
- prefect/client/base.py +10 -5
- prefect/client/orchestration.py +405 -85
- prefect/client/schemas/actions.py +4 -3
- prefect/client/schemas/objects.py +6 -5
- prefect/client/schemas/schedules.py +2 -6
- prefect/client/schemas/sorting.py +9 -0
- prefect/client/utilities.py +25 -3
- prefect/concurrency/asyncio.py +11 -5
- prefect/concurrency/events.py +3 -3
- prefect/concurrency/services.py +1 -1
- prefect/concurrency/sync.py +9 -5
- prefect/deployments/__init__.py +0 -2
- prefect/deployments/base.py +2 -144
- prefect/deployments/deployments.py +29 -20
- prefect/deployments/runner.py +36 -28
- prefect/deployments/steps/core.py +3 -3
- prefect/deprecated/packaging/serializers.py +5 -4
- prefect/engine.py +3 -1
- prefect/events/__init__.py +45 -0
- prefect/events/actions.py +250 -18
- prefect/events/cli/automations.py +201 -0
- prefect/events/clients.py +179 -21
- prefect/events/filters.py +30 -3
- prefect/events/instrument.py +40 -40
- prefect/events/related.py +2 -1
- prefect/events/schemas/automations.py +126 -8
- prefect/events/schemas/deployment_triggers.py +23 -277
- prefect/events/schemas/events.py +7 -7
- prefect/events/utilities.py +3 -1
- prefect/events/worker.py +21 -8
- prefect/exceptions.py +1 -1
- prefect/flows.py +33 -18
- prefect/input/actions.py +9 -9
- prefect/input/run_input.py +49 -37
- prefect/logging/__init__.py +2 -2
- prefect/logging/loggers.py +64 -1
- prefect/new_flow_engine.py +293 -0
- prefect/new_task_engine.py +374 -0
- prefect/results.py +32 -12
- prefect/runner/runner.py +3 -2
- prefect/serializers.py +62 -31
- prefect/server/api/collections_data/views/aggregate-worker-metadata.json +44 -3
- prefect/settings.py +32 -10
- prefect/states.py +25 -19
- prefect/tasks.py +17 -0
- prefect/types/__init__.py +90 -0
- prefect/utilities/asyncutils.py +37 -0
- prefect/utilities/engine.py +6 -4
- prefect/utilities/pydantic.py +34 -15
- prefect/utilities/schema_tools/hydration.py +88 -19
- prefect/utilities/schema_tools/validation.py +1 -1
- prefect/variables.py +4 -4
- {prefect_client-2.17.1.dist-info → prefect_client-2.18.1.dist-info}/METADATA +1 -1
- {prefect_client-2.17.1.dist-info → prefect_client-2.18.1.dist-info}/RECORD +71 -67
- /prefect/{concurrency/common.py → events/cli/__init__.py} +0 -0
- {prefect_client-2.17.1.dist-info → prefect_client-2.18.1.dist-info}/LICENSE +0 -0
- {prefect_client-2.17.1.dist-info → prefect_client-2.18.1.dist-info}/WHEEL +0 -0
- {prefect_client-2.17.1.dist-info → prefect_client-2.18.1.dist-info}/top_level.txt +0 -0
prefect/events/clients.py
CHANGED
@@ -7,17 +7,19 @@ from typing import (
|
|
7
7
|
ClassVar,
|
8
8
|
Dict,
|
9
9
|
List,
|
10
|
-
|
10
|
+
MutableMapping,
|
11
11
|
Optional,
|
12
12
|
Tuple,
|
13
13
|
Type,
|
14
14
|
)
|
15
15
|
from uuid import UUID
|
16
16
|
|
17
|
+
import httpx
|
17
18
|
import orjson
|
18
19
|
import pendulum
|
19
20
|
from cachetools import TTLCache
|
20
21
|
from typing_extensions import Self
|
22
|
+
from websockets import Subprotocol
|
21
23
|
from websockets.client import WebSocketClientProtocol, connect
|
22
24
|
from websockets.exceptions import (
|
23
25
|
ConnectionClosed,
|
@@ -25,9 +27,15 @@ from websockets.exceptions import (
|
|
25
27
|
ConnectionClosedOK,
|
26
28
|
)
|
27
29
|
|
30
|
+
from prefect.client.base import PrefectHttpxClient
|
28
31
|
from prefect.events import Event
|
29
32
|
from prefect.logging import get_logger
|
30
|
-
from prefect.settings import
|
33
|
+
from prefect.settings import (
|
34
|
+
PREFECT_API_KEY,
|
35
|
+
PREFECT_API_URL,
|
36
|
+
PREFECT_CLOUD_API_URL,
|
37
|
+
PREFECT_EXPERIMENTAL_EVENTS,
|
38
|
+
)
|
31
39
|
|
32
40
|
if TYPE_CHECKING:
|
33
41
|
from prefect.events.filters import EventFilter
|
@@ -35,6 +43,53 @@ if TYPE_CHECKING:
|
|
35
43
|
logger = get_logger(__name__)
|
36
44
|
|
37
45
|
|
46
|
+
def get_events_client(
|
47
|
+
reconnection_attempts: int = 10,
|
48
|
+
checkpoint_every: int = 20,
|
49
|
+
) -> "EventsClient":
|
50
|
+
api_url = PREFECT_API_URL.value()
|
51
|
+
if isinstance(api_url, str) and api_url.startswith(PREFECT_CLOUD_API_URL.value()):
|
52
|
+
return PrefectCloudEventsClient(
|
53
|
+
reconnection_attempts=reconnection_attempts,
|
54
|
+
checkpoint_every=checkpoint_every,
|
55
|
+
)
|
56
|
+
elif PREFECT_EXPERIMENTAL_EVENTS:
|
57
|
+
if PREFECT_API_URL:
|
58
|
+
return PrefectEventsClient(
|
59
|
+
reconnection_attempts=reconnection_attempts,
|
60
|
+
checkpoint_every=checkpoint_every,
|
61
|
+
)
|
62
|
+
else:
|
63
|
+
return PrefectEphemeralEventsClient()
|
64
|
+
|
65
|
+
raise RuntimeError(
|
66
|
+
"The current server and client configuration does not support "
|
67
|
+
"events. Enable experimental events support with the "
|
68
|
+
"PREFECT_EXPERIMENTAL_EVENTS setting."
|
69
|
+
)
|
70
|
+
|
71
|
+
|
72
|
+
def get_events_subscriber(
|
73
|
+
filter: Optional["EventFilter"] = None,
|
74
|
+
reconnection_attempts: int = 10,
|
75
|
+
) -> "PrefectEventSubscriber":
|
76
|
+
api_url = PREFECT_API_URL.value()
|
77
|
+
if isinstance(api_url, str) and api_url.startswith(PREFECT_CLOUD_API_URL.value()):
|
78
|
+
return PrefectCloudEventSubscriber(
|
79
|
+
filter=filter, reconnection_attempts=reconnection_attempts
|
80
|
+
)
|
81
|
+
elif PREFECT_EXPERIMENTAL_EVENTS:
|
82
|
+
return PrefectEventSubscriber(
|
83
|
+
filter=filter, reconnection_attempts=reconnection_attempts
|
84
|
+
)
|
85
|
+
|
86
|
+
raise RuntimeError(
|
87
|
+
"The current server and client configuration does not support "
|
88
|
+
"events. Enable experimental events support with the "
|
89
|
+
"PREFECT_EXPERIMENTAL_EVENTS setting."
|
90
|
+
)
|
91
|
+
|
92
|
+
|
38
93
|
class EventsClient(abc.ABC):
|
39
94
|
"""The abstract interface for all Prefect Events clients"""
|
40
95
|
|
@@ -51,7 +106,7 @@ class EventsClient(abc.ABC):
|
|
51
106
|
async def _emit(self, event: Event) -> None: # pragma: no cover
|
52
107
|
...
|
53
108
|
|
54
|
-
async def __aenter__(self) ->
|
109
|
+
async def __aenter__(self) -> Self:
|
55
110
|
self._in_context = True
|
56
111
|
return self
|
57
112
|
|
@@ -99,7 +154,7 @@ class AssertingEventsClient(EventsClient):
|
|
99
154
|
async def _emit(self, event: Event) -> None:
|
100
155
|
self.events.append(event)
|
101
156
|
|
102
|
-
async def __aenter__(self) ->
|
157
|
+
async def __aenter__(self) -> Self:
|
103
158
|
await super().__aenter__()
|
104
159
|
self.events = []
|
105
160
|
return self
|
@@ -119,6 +174,52 @@ def _get_api_url_and_key(
|
|
119
174
|
return api_url, api_key
|
120
175
|
|
121
176
|
|
177
|
+
class PrefectEphemeralEventsClient(EventsClient):
|
178
|
+
"""A Prefect Events client that sends events to an ephemeral Prefect server"""
|
179
|
+
|
180
|
+
def __init__(self):
|
181
|
+
if not PREFECT_EXPERIMENTAL_EVENTS:
|
182
|
+
raise ValueError(
|
183
|
+
"PrefectEphemeralEventsClient can only be used when "
|
184
|
+
"PREFECT_EXPERIMENTAL_EVENTS is set to True"
|
185
|
+
)
|
186
|
+
if PREFECT_API_KEY.value():
|
187
|
+
raise ValueError(
|
188
|
+
"PrefectEphemeralEventsClient cannot be used when PREFECT_API_KEY is set."
|
189
|
+
" Please use PrefectEventsClient or PrefectCloudEventsClient instead."
|
190
|
+
)
|
191
|
+
from prefect.server.api.server import create_app
|
192
|
+
|
193
|
+
app = create_app()
|
194
|
+
|
195
|
+
self._http_client = PrefectHttpxClient(
|
196
|
+
transport=httpx.ASGITransport(app=app, raise_app_exceptions=False),
|
197
|
+
base_url="http://ephemeral-prefect/api",
|
198
|
+
enable_csrf_support=False,
|
199
|
+
)
|
200
|
+
|
201
|
+
async def __aenter__(self) -> Self:
|
202
|
+
await super().__aenter__()
|
203
|
+
await self._http_client.__aenter__()
|
204
|
+
return self
|
205
|
+
|
206
|
+
async def __aexit__(
|
207
|
+
self,
|
208
|
+
exc_type: Optional[Type[Exception]],
|
209
|
+
exc_val: Optional[Exception],
|
210
|
+
exc_tb: Optional[TracebackType],
|
211
|
+
) -> None:
|
212
|
+
self._websocket = None
|
213
|
+
await self._http_client.__aexit__(exc_type, exc_val, exc_tb)
|
214
|
+
return await super().__aexit__(exc_type, exc_val, exc_tb)
|
215
|
+
|
216
|
+
async def _emit(self, event: Event) -> None:
|
217
|
+
await self._http_client.post(
|
218
|
+
"/events",
|
219
|
+
json=[event.dict(json_compatible=True)],
|
220
|
+
)
|
221
|
+
|
222
|
+
|
122
223
|
class PrefectEventsClient(EventsClient):
|
123
224
|
"""A Prefect Events client that streams events to a Prefect server"""
|
124
225
|
|
@@ -127,7 +228,7 @@ class PrefectEventsClient(EventsClient):
|
|
127
228
|
|
128
229
|
def __init__(
|
129
230
|
self,
|
130
|
-
api_url: str = None,
|
231
|
+
api_url: Optional[str] = None,
|
131
232
|
reconnection_attempts: int = 10,
|
132
233
|
checkpoint_every: int = 20,
|
133
234
|
):
|
@@ -243,8 +344,8 @@ class PrefectCloudEventsClient(PrefectEventsClient):
|
|
243
344
|
|
244
345
|
def __init__(
|
245
346
|
self,
|
246
|
-
api_url: str = None,
|
247
|
-
api_key: str = None,
|
347
|
+
api_url: Optional[str] = None,
|
348
|
+
api_key: Optional[str] = None,
|
248
349
|
reconnection_attempts: int = 10,
|
249
350
|
checkpoint_every: int = 20,
|
250
351
|
):
|
@@ -274,18 +375,18 @@ SEEN_EVENTS_SIZE = 500_000
|
|
274
375
|
SEEN_EVENTS_TTL = 120
|
275
376
|
|
276
377
|
|
277
|
-
class
|
378
|
+
class PrefectEventSubscriber:
|
278
379
|
"""
|
279
380
|
Subscribes to a Prefect Cloud event stream, yielding events as they occur.
|
280
381
|
|
281
382
|
Example:
|
282
383
|
|
283
|
-
from prefect.events.clients import
|
384
|
+
from prefect.events.clients import PrefectEventSubscriber
|
284
385
|
from prefect.events.filters import EventFilter, EventNameFilter
|
285
386
|
|
286
387
|
filter = EventFilter(event=EventNameFilter(prefix=["prefect.flow-run."]))
|
287
388
|
|
288
|
-
async with
|
389
|
+
async with PrefectEventSubscriber(filter=filter) as subscriber:
|
289
390
|
async for event in subscriber:
|
290
391
|
print(event.occurred, event.resource.id, event.event)
|
291
392
|
|
@@ -293,13 +394,14 @@ class PrefectCloudEventSubscriber:
|
|
293
394
|
|
294
395
|
_websocket: Optional[WebSocketClientProtocol]
|
295
396
|
_filter: "EventFilter"
|
296
|
-
_seen_events:
|
397
|
+
_seen_events: MutableMapping[UUID, bool]
|
398
|
+
|
399
|
+
_api_key: Optional[str]
|
297
400
|
|
298
401
|
def __init__(
|
299
402
|
self,
|
300
|
-
api_url: str = None,
|
301
|
-
|
302
|
-
filter: "EventFilter" = None,
|
403
|
+
api_url: Optional[str] = None,
|
404
|
+
filter: Optional["EventFilter"] = None,
|
303
405
|
reconnection_attempts: int = 10,
|
304
406
|
):
|
305
407
|
"""
|
@@ -309,11 +411,13 @@ class PrefectCloudEventSubscriber:
|
|
309
411
|
reconnection_attempts: When the client is disconnected, how many times
|
310
412
|
the client should attempt to reconnect
|
311
413
|
"""
|
312
|
-
|
414
|
+
if not api_url:
|
415
|
+
api_url = PREFECT_API_URL.value()
|
416
|
+
self._api_key = None
|
313
417
|
|
314
418
|
from prefect.events.filters import EventFilter
|
315
419
|
|
316
|
-
self._filter = filter or EventFilter()
|
420
|
+
self._filter = filter or EventFilter() # type: ignore[call-arg]
|
317
421
|
self._seen_events = TTLCache(maxsize=SEEN_EVENTS_SIZE, ttl=SEEN_EVENTS_TTL)
|
318
422
|
|
319
423
|
socket_url = (
|
@@ -324,17 +428,16 @@ class PrefectCloudEventSubscriber:
|
|
324
428
|
|
325
429
|
logger.debug("Connecting to %s", socket_url)
|
326
430
|
|
327
|
-
self._api_key = api_key
|
328
431
|
self._connect = connect(
|
329
432
|
socket_url,
|
330
|
-
subprotocols=["prefect"],
|
433
|
+
subprotocols=[Subprotocol("prefect")],
|
331
434
|
)
|
332
435
|
self._websocket = None
|
333
436
|
self._reconnection_attempts = reconnection_attempts
|
334
437
|
if self._reconnection_attempts < 0:
|
335
438
|
raise ValueError("reconnection_attempts must be a non-negative integer")
|
336
439
|
|
337
|
-
async def __aenter__(self) ->
|
440
|
+
async def __aenter__(self) -> Self:
|
338
441
|
# Don't handle any errors in the initial connection, because these are most
|
339
442
|
# likely a permission or configuration issue that should propagate
|
340
443
|
await self._reconnect()
|
@@ -398,7 +501,7 @@ class PrefectCloudEventSubscriber:
|
|
398
501
|
self._websocket = None
|
399
502
|
await self._connect.__aexit__(exc_type, exc_val, exc_tb)
|
400
503
|
|
401
|
-
def __aiter__(self) ->
|
504
|
+
def __aiter__(self) -> Self:
|
402
505
|
return self
|
403
506
|
|
404
507
|
async def __anext__(self) -> Event:
|
@@ -416,7 +519,7 @@ class PrefectCloudEventSubscriber:
|
|
416
519
|
|
417
520
|
while True:
|
418
521
|
message = orjson.loads(await self._websocket.recv())
|
419
|
-
event = Event.parse_obj(message["event"])
|
522
|
+
event: Event = Event.parse_obj(message["event"])
|
420
523
|
|
421
524
|
if event.id in self._seen_events:
|
422
525
|
continue
|
@@ -441,3 +544,58 @@ class PrefectCloudEventSubscriber:
|
|
441
544
|
# a standard load balancer timeout, but after that, just take a
|
442
545
|
# beat to let things come back around.
|
443
546
|
await asyncio.sleep(1)
|
547
|
+
raise StopAsyncIteration
|
548
|
+
|
549
|
+
|
550
|
+
class PrefectCloudEventSubscriber(PrefectEventSubscriber):
|
551
|
+
def __init__(
|
552
|
+
self,
|
553
|
+
api_url: Optional[str] = None,
|
554
|
+
api_key: Optional[str] = None,
|
555
|
+
filter: Optional["EventFilter"] = None,
|
556
|
+
reconnection_attempts: int = 10,
|
557
|
+
):
|
558
|
+
"""
|
559
|
+
Args:
|
560
|
+
api_url: The base URL for a Prefect Cloud workspace
|
561
|
+
api_key: The API of an actor with the manage_events scope
|
562
|
+
reconnection_attempts: When the client is disconnected, how many times
|
563
|
+
the client should attempt to reconnect
|
564
|
+
"""
|
565
|
+
api_url, api_key = _get_api_url_and_key(api_url, api_key)
|
566
|
+
|
567
|
+
super().__init__(
|
568
|
+
api_url=api_url,
|
569
|
+
filter=filter,
|
570
|
+
reconnection_attempts=reconnection_attempts,
|
571
|
+
)
|
572
|
+
|
573
|
+
self._api_key = api_key
|
574
|
+
|
575
|
+
|
576
|
+
class PrefectCloudAccountEventSubscriber(PrefectCloudEventSubscriber):
|
577
|
+
def __init__(
|
578
|
+
self,
|
579
|
+
api_url: Optional[str] = None,
|
580
|
+
api_key: Optional[str] = None,
|
581
|
+
filter: Optional["EventFilter"] = None,
|
582
|
+
reconnection_attempts: int = 10,
|
583
|
+
):
|
584
|
+
"""
|
585
|
+
Args:
|
586
|
+
api_url: The base URL for a Prefect Cloud workspace
|
587
|
+
api_key: The API of an actor with the manage_events scope
|
588
|
+
reconnection_attempts: When the client is disconnected, how many times
|
589
|
+
the client should attempt to reconnect
|
590
|
+
"""
|
591
|
+
api_url, api_key = _get_api_url_and_key(api_url, api_key)
|
592
|
+
|
593
|
+
account_api_url, _, _ = api_url.partition("/workspaces/")
|
594
|
+
|
595
|
+
super().__init__(
|
596
|
+
api_url=account_api_url,
|
597
|
+
filter=filter,
|
598
|
+
reconnection_attempts=reconnection_attempts,
|
599
|
+
)
|
600
|
+
|
601
|
+
self._api_key = api_key
|
prefect/events/filters.py
CHANGED
@@ -13,7 +13,34 @@ from .schemas.events import Event, Resource, ResourceSpecification
|
|
13
13
|
if HAS_PYDANTIC_V2:
|
14
14
|
from pydantic.v1 import Field, PrivateAttr
|
15
15
|
else:
|
16
|
-
from pydantic import Field, PrivateAttr
|
16
|
+
from pydantic import Field, PrivateAttr # type: ignore
|
17
|
+
|
18
|
+
|
19
|
+
class AutomationFilterCreated(PrefectBaseModel):
|
20
|
+
"""Filter by `Automation.created`."""
|
21
|
+
|
22
|
+
before_: Optional[DateTimeTZ] = Field(
|
23
|
+
default=None,
|
24
|
+
description="Only include automations created before this datetime",
|
25
|
+
)
|
26
|
+
|
27
|
+
|
28
|
+
class AutomationFilterName(PrefectBaseModel):
|
29
|
+
"""Filter by `Automation.created`."""
|
30
|
+
|
31
|
+
any_: Optional[List[str]] = Field(
|
32
|
+
default=None,
|
33
|
+
description="Only include automations with names that match any of these strings",
|
34
|
+
)
|
35
|
+
|
36
|
+
|
37
|
+
class AutomationFilter(PrefectBaseModel):
|
38
|
+
name: Optional[AutomationFilterName] = Field(
|
39
|
+
default=None, description="Filter criteria for `Automation.name`"
|
40
|
+
)
|
41
|
+
created: Optional[AutomationFilterCreated] = Field(
|
42
|
+
default=None, description="Filter criteria for `Automation.created`"
|
43
|
+
)
|
17
44
|
|
18
45
|
|
19
46
|
class EventDataFilter(PrefectBaseModel, extra="forbid"):
|
@@ -203,7 +230,7 @@ class EventOrder(AutoEnum):
|
|
203
230
|
|
204
231
|
class EventFilter(EventDataFilter):
|
205
232
|
occurred: EventOccurredFilter = Field(
|
206
|
-
default_factory=EventOccurredFilter,
|
233
|
+
default_factory=lambda: EventOccurredFilter(),
|
207
234
|
description="Filter criteria for when the events occurred",
|
208
235
|
)
|
209
236
|
event: Optional[EventNameFilter] = Field(
|
@@ -220,7 +247,7 @@ class EventFilter(EventDataFilter):
|
|
220
247
|
None, description="Filter criteria for the related resources of the event"
|
221
248
|
)
|
222
249
|
id: EventIDFilter = Field(
|
223
|
-
default_factory=EventIDFilter,
|
250
|
+
default_factory=lambda: EventIDFilter(id=[]),
|
224
251
|
description="Filter criteria for the events' ID",
|
225
252
|
)
|
226
253
|
|
prefect/events/instrument.py
CHANGED
@@ -10,7 +10,9 @@ from typing import (
|
|
10
10
|
Set,
|
11
11
|
Tuple,
|
12
12
|
Type,
|
13
|
+
TypeVar,
|
13
14
|
Union,
|
15
|
+
cast,
|
14
16
|
)
|
15
17
|
|
16
18
|
from prefect.events import emit_event
|
@@ -41,45 +43,45 @@ def emit_instance_method_called_event(
|
|
41
43
|
)
|
42
44
|
|
43
45
|
|
44
|
-
|
45
|
-
def instrument(function):
|
46
|
-
if is_instrumented(function):
|
47
|
-
return function
|
46
|
+
F = TypeVar("F", bound=Callable)
|
48
47
|
|
49
|
-
if inspect.iscoroutinefunction(function):
|
50
48
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
try:
|
55
|
-
return await function(self, *args, **kwargs)
|
56
|
-
except Exception as exc:
|
57
|
-
success = False
|
58
|
-
raise exc
|
59
|
-
finally:
|
60
|
-
emit_instance_method_called_event(
|
61
|
-
instance=self, method_name=function.__name__, successful=success
|
62
|
-
)
|
49
|
+
def instrument_instance_method_call(function: F) -> F:
|
50
|
+
if is_instrumented(function):
|
51
|
+
return function
|
63
52
|
|
64
|
-
|
53
|
+
if inspect.iscoroutinefunction(function):
|
65
54
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
55
|
+
@functools.wraps(function)
|
56
|
+
async def inner(self, *args, **kwargs):
|
57
|
+
success = True
|
58
|
+
try:
|
59
|
+
return await function(self, *args, **kwargs)
|
60
|
+
except Exception as exc:
|
61
|
+
success = False
|
62
|
+
raise exc
|
63
|
+
finally:
|
64
|
+
emit_instance_method_called_event(
|
65
|
+
instance=self, method_name=function.__name__, successful=success
|
66
|
+
)
|
78
67
|
|
79
|
-
|
80
|
-
return inner
|
68
|
+
else:
|
81
69
|
|
82
|
-
|
70
|
+
@functools.wraps(function)
|
71
|
+
def inner(self, *args, **kwargs):
|
72
|
+
success = True
|
73
|
+
try:
|
74
|
+
return function(self, *args, **kwargs)
|
75
|
+
except Exception as exc:
|
76
|
+
success = False
|
77
|
+
raise exc
|
78
|
+
finally:
|
79
|
+
emit_instance_method_called_event(
|
80
|
+
instance=self, method_name=function.__name__, successful=success
|
81
|
+
)
|
82
|
+
|
83
|
+
setattr(inner, "__events_instrumented__", True)
|
84
|
+
return cast(F, inner)
|
83
85
|
|
84
86
|
|
85
87
|
def is_instrumented(function: Callable) -> bool:
|
@@ -119,17 +121,15 @@ def instrument_method_calls_on_class_instances(cls: Type) -> Type:
|
|
119
121
|
"""
|
120
122
|
|
121
123
|
required_events_methods = ["_event_kind", "_event_method_called_resources"]
|
122
|
-
for
|
123
|
-
if not hasattr(cls,
|
124
|
+
for method_name in required_events_methods:
|
125
|
+
if not hasattr(cls, method_name):
|
124
126
|
raise RuntimeError(
|
125
|
-
f"Unable to instrument class {cls}. Class must define {
|
127
|
+
f"Unable to instrument class {cls}. Class must define {method_name!r}."
|
126
128
|
)
|
127
129
|
|
128
|
-
|
129
|
-
|
130
|
-
for name, method in instrumentable_methods(
|
130
|
+
for method_name, method in instrumentable_methods(
|
131
131
|
cls,
|
132
132
|
exclude_methods=getattr(cls, "_events_excluded_methods", []),
|
133
133
|
):
|
134
|
-
setattr(cls,
|
134
|
+
setattr(cls, method_name, instrument_instance_method_call(method))
|
135
135
|
return cls
|
prefect/events/related.py
CHANGED
@@ -57,6 +57,7 @@ async def related_resources_from_run_context(
|
|
57
57
|
exclude: Optional[Set[str]] = None,
|
58
58
|
) -> List[RelatedResource]:
|
59
59
|
from prefect.client.orchestration import get_client
|
60
|
+
from prefect.client.schemas.objects import FlowRun
|
60
61
|
from prefect.context import FlowRunContext, TaskRunContext
|
61
62
|
|
62
63
|
if exclude is None:
|
@@ -111,7 +112,7 @@ async def related_resources_from_run_context(
|
|
111
112
|
|
112
113
|
flow_run = related_objects[0]["object"]
|
113
114
|
|
114
|
-
if flow_run:
|
115
|
+
if isinstance(flow_run, FlowRun):
|
115
116
|
related_objects += list(
|
116
117
|
await asyncio.gather(
|
117
118
|
_get_and_cache_related_object(
|