prefect-client 3.0.0rc18__py3-none-any.whl → 3.0.0rc20__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/__init__.py +0 -3
- prefect/_internal/concurrency/services.py +14 -0
- prefect/_internal/schemas/bases.py +1 -0
- prefect/blocks/core.py +41 -30
- prefect/blocks/system.py +48 -12
- prefect/client/cloud.py +56 -7
- prefect/client/collections.py +1 -1
- prefect/client/orchestration.py +111 -8
- prefect/client/schemas/objects.py +40 -2
- prefect/concurrency/asyncio.py +8 -2
- prefect/concurrency/services.py +16 -6
- prefect/concurrency/sync.py +4 -1
- prefect/concurrency/v1/__init__.py +0 -0
- prefect/concurrency/v1/asyncio.py +143 -0
- prefect/concurrency/v1/context.py +27 -0
- prefect/concurrency/v1/events.py +61 -0
- prefect/concurrency/v1/services.py +116 -0
- prefect/concurrency/v1/sync.py +92 -0
- prefect/context.py +2 -2
- prefect/deployments/flow_runs.py +0 -7
- prefect/deployments/runner.py +11 -0
- prefect/events/clients.py +41 -0
- prefect/events/related.py +72 -73
- prefect/events/utilities.py +2 -0
- prefect/events/worker.py +12 -3
- prefect/exceptions.py +6 -0
- prefect/flow_engine.py +5 -0
- prefect/flows.py +9 -2
- prefect/logging/handlers.py +4 -1
- prefect/main.py +8 -6
- prefect/records/base.py +74 -18
- prefect/records/filesystem.py +207 -0
- prefect/records/memory.py +16 -3
- prefect/records/result_store.py +19 -14
- prefect/results.py +232 -169
- prefect/runner/runner.py +7 -4
- prefect/settings.py +14 -15
- prefect/states.py +73 -18
- prefect/task_engine.py +127 -221
- prefect/task_worker.py +7 -39
- prefect/tasks.py +0 -7
- prefect/transactions.py +89 -27
- prefect/utilities/annotations.py +4 -3
- prefect/utilities/asyncutils.py +4 -4
- prefect/utilities/callables.py +1 -3
- prefect/utilities/dispatch.py +16 -11
- prefect/utilities/engine.py +1 -4
- prefect/utilities/schema_tools/hydration.py +13 -0
- prefect/workers/base.py +78 -18
- {prefect_client-3.0.0rc18.dist-info → prefect_client-3.0.0rc20.dist-info}/METADATA +3 -4
- {prefect_client-3.0.0rc18.dist-info → prefect_client-3.0.0rc20.dist-info}/RECORD +54 -48
- prefect/manifests.py +0 -21
- {prefect_client-3.0.0rc18.dist-info → prefect_client-3.0.0rc20.dist-info}/LICENSE +0 -0
- {prefect_client-3.0.0rc18.dist-info → prefect_client-3.0.0rc20.dist-info}/WHEEL +0 -0
- {prefect_client-3.0.0rc18.dist-info → prefect_client-3.0.0rc20.dist-info}/top_level.txt +0 -0
prefect/results.py
CHANGED
@@ -17,7 +17,15 @@ from typing import (
|
|
17
17
|
)
|
18
18
|
from uuid import UUID
|
19
19
|
|
20
|
-
from pydantic import
|
20
|
+
from pydantic import (
|
21
|
+
BaseModel,
|
22
|
+
ConfigDict,
|
23
|
+
Field,
|
24
|
+
PrivateAttr,
|
25
|
+
ValidationError,
|
26
|
+
model_serializer,
|
27
|
+
model_validator,
|
28
|
+
)
|
21
29
|
from pydantic_core import PydanticUndefinedType
|
22
30
|
from pydantic_extra_types.pendulum_dt import DateTime
|
23
31
|
from typing_extensions import ParamSpec, Self
|
@@ -25,13 +33,13 @@ from typing_extensions import ParamSpec, Self
|
|
25
33
|
import prefect
|
26
34
|
from prefect.blocks.core import Block
|
27
35
|
from prefect.client.utilities import inject_client
|
28
|
-
from prefect.exceptions import
|
36
|
+
from prefect.exceptions import SerializationError
|
29
37
|
from prefect.filesystems import (
|
30
38
|
LocalFileSystem,
|
31
39
|
WritableFileSystem,
|
32
40
|
)
|
33
41
|
from prefect.logging import get_logger
|
34
|
-
from prefect.serializers import Serializer
|
42
|
+
from prefect.serializers import PickleSerializer, Serializer
|
35
43
|
from prefect.settings import (
|
36
44
|
PREFECT_DEFAULT_RESULT_STORAGE_BLOCK,
|
37
45
|
PREFECT_LOCAL_STORAGE_PATH,
|
@@ -332,22 +340,15 @@ class ResultFactory(BaseModel):
|
|
332
340
|
obj: R,
|
333
341
|
key: Optional[str] = None,
|
334
342
|
expiration: Optional[DateTime] = None,
|
335
|
-
defer_persistence: bool = False,
|
336
343
|
) -> Union[R, "BaseResult[R]"]:
|
337
344
|
"""
|
338
345
|
Create a result type for the given object.
|
339
346
|
|
340
|
-
If persistence is disabled, the object is wrapped in an `UnpersistedResult` and
|
341
|
-
returned.
|
342
|
-
|
343
347
|
If persistence is enabled the object is serialized, persisted to storage, and a reference is returned.
|
344
348
|
"""
|
345
349
|
# Null objects are "cached" in memory at no cost
|
346
350
|
should_cache_object = self.cache_result_in_memory or obj is None
|
347
351
|
|
348
|
-
if not self.persist_result:
|
349
|
-
return await UnpersistedResult.create(obj, cache_object=should_cache_object)
|
350
|
-
|
351
352
|
if key:
|
352
353
|
|
353
354
|
def key_fn():
|
@@ -365,23 +366,198 @@ class ResultFactory(BaseModel):
|
|
365
366
|
serializer=self.serializer,
|
366
367
|
cache_object=should_cache_object,
|
367
368
|
expiration=expiration,
|
368
|
-
|
369
|
+
serialize_to_none=not self.persist_result,
|
369
370
|
)
|
370
371
|
|
372
|
+
# TODO: These two methods need to find a new home
|
373
|
+
|
371
374
|
@sync_compatible
|
372
375
|
async def store_parameters(self, identifier: UUID, parameters: Dict[str, Any]):
|
373
|
-
|
374
|
-
|
376
|
+
record = ResultRecord(
|
377
|
+
result=parameters,
|
378
|
+
metadata=ResultRecordMetadata(
|
379
|
+
serializer=self.serializer, storage_key=str(identifier)
|
380
|
+
),
|
381
|
+
)
|
375
382
|
await self.storage_block.write_path(
|
376
|
-
f"parameters/{identifier}", content=
|
383
|
+
f"parameters/{identifier}", content=record.serialize()
|
377
384
|
)
|
378
385
|
|
379
386
|
@sync_compatible
|
380
387
|
async def read_parameters(self, identifier: UUID) -> Dict[str, Any]:
|
381
|
-
|
388
|
+
record = ResultRecord.deserialize(
|
382
389
|
await self.storage_block.read_path(f"parameters/{identifier}")
|
383
390
|
)
|
384
|
-
return
|
391
|
+
return record.result
|
392
|
+
|
393
|
+
|
394
|
+
class ResultRecordMetadata(BaseModel):
|
395
|
+
"""
|
396
|
+
Metadata for a result record.
|
397
|
+
"""
|
398
|
+
|
399
|
+
storage_key: Optional[str] = Field(
|
400
|
+
default=None
|
401
|
+
) # optional for backwards compatibility
|
402
|
+
expiration: Optional[DateTime] = Field(default=None)
|
403
|
+
serializer: Serializer = Field(default_factory=PickleSerializer)
|
404
|
+
prefect_version: str = Field(default=prefect.__version__)
|
405
|
+
|
406
|
+
def dump_bytes(self) -> bytes:
|
407
|
+
"""
|
408
|
+
Serialize the metadata to bytes.
|
409
|
+
|
410
|
+
Returns:
|
411
|
+
bytes: the serialized metadata
|
412
|
+
"""
|
413
|
+
return self.model_dump_json(serialize_as_any=True).encode()
|
414
|
+
|
415
|
+
@classmethod
|
416
|
+
def load_bytes(cls, data: bytes) -> "ResultRecordMetadata":
|
417
|
+
"""
|
418
|
+
Deserialize metadata from bytes.
|
419
|
+
|
420
|
+
Args:
|
421
|
+
data: the serialized metadata
|
422
|
+
|
423
|
+
Returns:
|
424
|
+
ResultRecordMetadata: the deserialized metadata
|
425
|
+
"""
|
426
|
+
return cls.model_validate_json(data)
|
427
|
+
|
428
|
+
|
429
|
+
class ResultRecord(BaseModel, Generic[R]):
|
430
|
+
"""
|
431
|
+
A record of a result.
|
432
|
+
"""
|
433
|
+
|
434
|
+
metadata: ResultRecordMetadata
|
435
|
+
result: R
|
436
|
+
|
437
|
+
@property
|
438
|
+
def expiration(self) -> Optional[DateTime]:
|
439
|
+
return self.metadata.expiration
|
440
|
+
|
441
|
+
@property
|
442
|
+
def serializer(self) -> Serializer:
|
443
|
+
return self.metadata.serializer
|
444
|
+
|
445
|
+
def serialize_result(self) -> bytes:
|
446
|
+
try:
|
447
|
+
data = self.serializer.dumps(self.result)
|
448
|
+
except Exception as exc:
|
449
|
+
extra_info = (
|
450
|
+
'You can try a different serializer (e.g. result_serializer="json") '
|
451
|
+
"or disabling persistence (persist_result=False) for this flow or task."
|
452
|
+
)
|
453
|
+
# check if this is a known issue with cloudpickle and pydantic
|
454
|
+
# and add extra information to help the user recover
|
455
|
+
|
456
|
+
if (
|
457
|
+
isinstance(exc, TypeError)
|
458
|
+
and isinstance(self.result, BaseModel)
|
459
|
+
and str(exc).startswith("cannot pickle")
|
460
|
+
):
|
461
|
+
try:
|
462
|
+
from IPython import get_ipython
|
463
|
+
|
464
|
+
if get_ipython() is not None:
|
465
|
+
extra_info = inspect.cleandoc(
|
466
|
+
"""
|
467
|
+
This is a known issue in Pydantic that prevents
|
468
|
+
locally-defined (non-imported) models from being
|
469
|
+
serialized by cloudpickle in IPython/Jupyter
|
470
|
+
environments. Please see
|
471
|
+
https://github.com/pydantic/pydantic/issues/8232 for
|
472
|
+
more information. To fix the issue, either: (1) move
|
473
|
+
your Pydantic class definition to an importable
|
474
|
+
location, (2) use the JSON serializer for your flow
|
475
|
+
or task (`result_serializer="json"`), or (3)
|
476
|
+
disable result persistence for your flow or task
|
477
|
+
(`persist_result=False`).
|
478
|
+
"""
|
479
|
+
).replace("\n", " ")
|
480
|
+
except ImportError:
|
481
|
+
pass
|
482
|
+
raise SerializationError(
|
483
|
+
f"Failed to serialize object of type {type(self.result).__name__!r} with "
|
484
|
+
f"serializer {self.serializer.type!r}. {extra_info}"
|
485
|
+
) from exc
|
486
|
+
|
487
|
+
return data
|
488
|
+
|
489
|
+
@model_validator(mode="before")
|
490
|
+
@classmethod
|
491
|
+
def coerce_old_format(cls, value: Any):
|
492
|
+
if isinstance(value, dict):
|
493
|
+
if "data" in value:
|
494
|
+
value["result"] = value.pop("data")
|
495
|
+
if "metadata" not in value:
|
496
|
+
value["metadata"] = {}
|
497
|
+
if "expiration" in value:
|
498
|
+
value["metadata"]["expiration"] = value.pop("expiration")
|
499
|
+
if "serializer" in value:
|
500
|
+
value["metadata"]["serializer"] = value.pop("serializer")
|
501
|
+
if "prefect_version" in value:
|
502
|
+
value["metadata"]["prefect_version"] = value.pop("prefect_version")
|
503
|
+
return value
|
504
|
+
|
505
|
+
def serialize_metadata(self) -> bytes:
|
506
|
+
return self.metadata.dump_bytes()
|
507
|
+
|
508
|
+
def serialize(
|
509
|
+
self,
|
510
|
+
) -> bytes:
|
511
|
+
"""
|
512
|
+
Serialize the record to bytes.
|
513
|
+
|
514
|
+
Returns:
|
515
|
+
bytes: the serialized record
|
516
|
+
|
517
|
+
"""
|
518
|
+
return (
|
519
|
+
self.model_copy(update={"result": self.serialize_result()})
|
520
|
+
.model_dump_json(serialize_as_any=True)
|
521
|
+
.encode()
|
522
|
+
)
|
523
|
+
|
524
|
+
@classmethod
|
525
|
+
def deserialize(cls, data: bytes) -> "ResultRecord[R]":
|
526
|
+
"""
|
527
|
+
Deserialize a record from bytes.
|
528
|
+
|
529
|
+
Args:
|
530
|
+
data: the serialized record
|
531
|
+
|
532
|
+
Returns:
|
533
|
+
ResultRecord: the deserialized record
|
534
|
+
"""
|
535
|
+
instance = cls.model_validate_json(data)
|
536
|
+
if isinstance(instance.result, bytes):
|
537
|
+
instance.result = instance.serializer.loads(instance.result)
|
538
|
+
elif isinstance(instance.result, str):
|
539
|
+
instance.result = instance.serializer.loads(instance.result.encode())
|
540
|
+
return instance
|
541
|
+
|
542
|
+
@classmethod
|
543
|
+
def deserialize_from_result_and_metadata(
|
544
|
+
cls, result: bytes, metadata: bytes
|
545
|
+
) -> "ResultRecord[R]":
|
546
|
+
"""
|
547
|
+
Deserialize a record from separate result and metadata bytes.
|
548
|
+
|
549
|
+
Args:
|
550
|
+
result: the result
|
551
|
+
metadata: the serialized metadata
|
552
|
+
|
553
|
+
Returns:
|
554
|
+
ResultRecord: the deserialized record
|
555
|
+
"""
|
556
|
+
result_record_metadata = ResultRecordMetadata.load_bytes(metadata)
|
557
|
+
return cls(
|
558
|
+
metadata=result_record_metadata,
|
559
|
+
result=result_record_metadata.serializer.loads(result),
|
560
|
+
)
|
385
561
|
|
386
562
|
|
387
563
|
@register_base_type
|
@@ -432,40 +608,12 @@ class BaseResult(BaseModel, abc.ABC, Generic[R]):
|
|
432
608
|
return cls.__name__ if isinstance(default, PydanticUndefinedType) else default
|
433
609
|
|
434
610
|
|
435
|
-
class UnpersistedResult(BaseResult):
|
436
|
-
"""
|
437
|
-
Result type for results that are not persisted outside of local memory.
|
438
|
-
"""
|
439
|
-
|
440
|
-
type: str = "unpersisted"
|
441
|
-
|
442
|
-
@sync_compatible
|
443
|
-
async def get(self) -> R:
|
444
|
-
if self.has_cached_object():
|
445
|
-
return self._cache
|
446
|
-
|
447
|
-
raise MissingResult("The result was not persisted and is no longer available.")
|
448
|
-
|
449
|
-
@classmethod
|
450
|
-
@sync_compatible
|
451
|
-
async def create(
|
452
|
-
cls: "Type[UnpersistedResult]",
|
453
|
-
obj: R,
|
454
|
-
cache_object: bool = True,
|
455
|
-
) -> "UnpersistedResult[R]":
|
456
|
-
result = cls()
|
457
|
-
# Only store the object in local memory, it will not be sent to the API
|
458
|
-
if cache_object:
|
459
|
-
result._cache_object(obj)
|
460
|
-
return result
|
461
|
-
|
462
|
-
|
463
611
|
class PersistedResult(BaseResult):
|
464
612
|
"""
|
465
613
|
Result type which stores a reference to a persisted result.
|
466
614
|
|
467
615
|
When created, the user's object is serialized and stored. The format for the content
|
468
|
-
is defined by `
|
616
|
+
is defined by `ResultRecord`. This reference contains metadata necessary for retrieval
|
469
617
|
of the object, such as a reference to the storage block and the key where the
|
470
618
|
content was written.
|
471
619
|
"""
|
@@ -476,12 +624,19 @@ class PersistedResult(BaseResult):
|
|
476
624
|
storage_key: str
|
477
625
|
storage_block_id: Optional[uuid.UUID] = None
|
478
626
|
expiration: Optional[DateTime] = None
|
627
|
+
serialize_to_none: bool = False
|
479
628
|
|
480
|
-
_should_cache_object: bool = PrivateAttr(default=True)
|
481
629
|
_persisted: bool = PrivateAttr(default=False)
|
630
|
+
_should_cache_object: bool = PrivateAttr(default=True)
|
482
631
|
_storage_block: WritableFileSystem = PrivateAttr(default=None)
|
483
632
|
_serializer: Serializer = PrivateAttr(default=None)
|
484
633
|
|
634
|
+
@model_serializer(mode="wrap")
|
635
|
+
def serialize_model(self, handler, info):
|
636
|
+
if self.serialize_to_none:
|
637
|
+
return None
|
638
|
+
return handler(self, info)
|
639
|
+
|
485
640
|
def _cache_object(
|
486
641
|
self,
|
487
642
|
obj: Any,
|
@@ -512,21 +667,20 @@ class PersistedResult(BaseResult):
|
|
512
667
|
if self.has_cached_object():
|
513
668
|
return self._cache
|
514
669
|
|
515
|
-
|
516
|
-
|
517
|
-
self.expiration = blob.expiration
|
670
|
+
record = await self._read_result_record(client=client)
|
671
|
+
self.expiration = record.expiration
|
518
672
|
|
519
673
|
if self._should_cache_object:
|
520
|
-
self._cache_object(
|
674
|
+
self._cache_object(record.result)
|
521
675
|
|
522
|
-
return
|
676
|
+
return record.result
|
523
677
|
|
524
678
|
@inject_client
|
525
|
-
async def
|
679
|
+
async def _read_result_record(self, client: "PrefectClient") -> "ResultRecord":
|
526
680
|
block = await self._get_storage_block(client=client)
|
527
681
|
content = await block.read_path(self.storage_key)
|
528
|
-
|
529
|
-
return
|
682
|
+
record = ResultRecord.deserialize(content)
|
683
|
+
return record
|
530
684
|
|
531
685
|
@staticmethod
|
532
686
|
def _infer_path(storage_block, key) -> str:
|
@@ -547,7 +701,7 @@ class PersistedResult(BaseResult):
|
|
547
701
|
Write the result to the storage block.
|
548
702
|
"""
|
549
703
|
|
550
|
-
if self._persisted:
|
704
|
+
if self._persisted or self.serialize_to_none:
|
551
705
|
# don't double write or overwrite
|
552
706
|
return
|
553
707
|
|
@@ -567,50 +721,15 @@ class PersistedResult(BaseResult):
|
|
567
721
|
# this could error if the serializer requires kwargs
|
568
722
|
serializer = Serializer(type=self.serializer_type)
|
569
723
|
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
)
|
577
|
-
# check if this is a known issue with cloudpickle and pydantic
|
578
|
-
# and add extra information to help the user recover
|
579
|
-
|
580
|
-
if (
|
581
|
-
isinstance(exc, TypeError)
|
582
|
-
and isinstance(obj, BaseModel)
|
583
|
-
and str(exc).startswith("cannot pickle")
|
584
|
-
):
|
585
|
-
try:
|
586
|
-
from IPython import get_ipython
|
587
|
-
|
588
|
-
if get_ipython() is not None:
|
589
|
-
extra_info = inspect.cleandoc(
|
590
|
-
"""
|
591
|
-
This is a known issue in Pydantic that prevents
|
592
|
-
locally-defined (non-imported) models from being
|
593
|
-
serialized by cloudpickle in IPython/Jupyter
|
594
|
-
environments. Please see
|
595
|
-
https://github.com/pydantic/pydantic/issues/8232 for
|
596
|
-
more information. To fix the issue, either: (1) move
|
597
|
-
your Pydantic class definition to an importable
|
598
|
-
location, (2) use the JSON serializer for your flow
|
599
|
-
or task (`result_serializer="json"`), or (3)
|
600
|
-
disable result persistence for your flow or task
|
601
|
-
(`persist_result=False`).
|
602
|
-
"""
|
603
|
-
).replace("\n", " ")
|
604
|
-
except ImportError:
|
605
|
-
pass
|
606
|
-
raise ValueError(
|
607
|
-
f"Failed to serialize object of type {type(obj).__name__!r} with "
|
608
|
-
f"serializer {serializer.type!r}. {extra_info}"
|
609
|
-
) from exc
|
610
|
-
blob = PersistedResultBlob(
|
611
|
-
serializer=serializer, data=data, expiration=self.expiration
|
724
|
+
record = ResultRecord(
|
725
|
+
result=obj,
|
726
|
+
metadata=ResultRecordMetadata(
|
727
|
+
storage_key=self.storage_key,
|
728
|
+
expiration=self.expiration,
|
729
|
+
serializer=serializer,
|
730
|
+
),
|
612
731
|
)
|
613
|
-
await storage_block.write_path(self.storage_key, content=
|
732
|
+
await storage_block.write_path(self.storage_key, content=record.serialize())
|
614
733
|
self._persisted = True
|
615
734
|
|
616
735
|
if not self._should_cache_object:
|
@@ -627,7 +746,7 @@ class PersistedResult(BaseResult):
|
|
627
746
|
storage_block_id: Optional[uuid.UUID] = None,
|
628
747
|
cache_object: bool = True,
|
629
748
|
expiration: Optional[DateTime] = None,
|
630
|
-
|
749
|
+
serialize_to_none: bool = False,
|
631
750
|
) -> "PersistedResult[R]":
|
632
751
|
"""
|
633
752
|
Create a new result reference from a user's object.
|
@@ -651,79 +770,23 @@ class PersistedResult(BaseResult):
|
|
651
770
|
storage_block_id=storage_block_id,
|
652
771
|
storage_key=key,
|
653
772
|
expiration=expiration,
|
773
|
+
serialize_to_none=serialize_to_none,
|
654
774
|
)
|
655
775
|
|
656
|
-
if cache_object and not defer_persistence:
|
657
|
-
# Attach the object to the result so it's available without deserialization
|
658
|
-
result._cache_object(
|
659
|
-
obj, storage_block=storage_block, serializer=serializer
|
660
|
-
)
|
661
|
-
|
662
776
|
object.__setattr__(result, "_should_cache_object", cache_object)
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
else:
|
667
|
-
# we must cache temporarily to allow for writing later
|
668
|
-
# the cache will be removed on write
|
669
|
-
result._cache_object(
|
670
|
-
obj, storage_block=storage_block, serializer=serializer
|
671
|
-
)
|
777
|
+
# we must cache temporarily to allow for writing later
|
778
|
+
# the cache will be removed on write
|
779
|
+
result._cache_object(obj, storage_block=storage_block, serializer=serializer)
|
672
780
|
|
673
781
|
return result
|
674
782
|
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
prefect_version: str = Field(default=prefect.__version__)
|
686
|
-
expiration: Optional[DateTime] = None
|
687
|
-
|
688
|
-
def load(self) -> Any:
|
689
|
-
return self.serializer.loads(self.data)
|
690
|
-
|
691
|
-
def to_bytes(self) -> bytes:
|
692
|
-
return self.model_dump_json(serialize_as_any=True).encode()
|
693
|
-
|
694
|
-
|
695
|
-
class UnknownResult(BaseResult):
|
696
|
-
"""
|
697
|
-
Result type for unknown results. Typically used to represent the result
|
698
|
-
of tasks that were forced from a failure state into a completed state.
|
699
|
-
|
700
|
-
The value for this result is always None and is not persisted to external
|
701
|
-
result storage, but orchestration treats the result the same as persisted
|
702
|
-
results when determining orchestration rules, such as whether to rerun a
|
703
|
-
completed task.
|
704
|
-
"""
|
705
|
-
|
706
|
-
type: str = "unknown"
|
707
|
-
value: None
|
708
|
-
|
709
|
-
def has_cached_object(self) -> bool:
|
710
|
-
# This result type always has the object cached in memory
|
711
|
-
return True
|
712
|
-
|
713
|
-
@sync_compatible
|
714
|
-
async def get(self) -> R:
|
715
|
-
return self.value
|
716
|
-
|
717
|
-
@classmethod
|
718
|
-
@sync_compatible
|
719
|
-
async def create(
|
720
|
-
cls: "Type[UnknownResult]",
|
721
|
-
obj: R = None,
|
722
|
-
) -> "UnknownResult[R]":
|
723
|
-
if obj is not None:
|
724
|
-
raise TypeError(
|
725
|
-
f"Unsupported type {type(obj).__name__!r} for unknown result. "
|
726
|
-
"Only None is supported."
|
727
|
-
)
|
728
|
-
|
729
|
-
return cls(value=obj)
|
783
|
+
def __eq__(self, other):
|
784
|
+
if not isinstance(other, PersistedResult):
|
785
|
+
return False
|
786
|
+
return (
|
787
|
+
self.type == other.type
|
788
|
+
and self.serializer_type == other.serializer_type
|
789
|
+
and self.storage_key == other.storage_key
|
790
|
+
and self.storage_block_id == other.storage_block_id
|
791
|
+
and self.expiration == other.expiration
|
792
|
+
)
|
prefect/runner/runner.py
CHANGED
@@ -35,7 +35,6 @@ import datetime
|
|
35
35
|
import inspect
|
36
36
|
import logging
|
37
37
|
import os
|
38
|
-
import shlex
|
39
38
|
import shutil
|
40
39
|
import signal
|
41
40
|
import subprocess
|
@@ -90,7 +89,11 @@ from prefect.utilities.asyncutils import (
|
|
90
89
|
sync_compatible,
|
91
90
|
)
|
92
91
|
from prefect.utilities.engine import propose_state
|
93
|
-
from prefect.utilities.processutils import
|
92
|
+
from prefect.utilities.processutils import (
|
93
|
+
_register_signal,
|
94
|
+
get_sys_executable,
|
95
|
+
run_process,
|
96
|
+
)
|
94
97
|
from prefect.utilities.services import (
|
95
98
|
critical_service_loop,
|
96
99
|
start_client_metrics_server,
|
@@ -527,7 +530,7 @@ class Runner:
|
|
527
530
|
task_status: anyio task status used to send a message to the caller
|
528
531
|
than the flow run process has started.
|
529
532
|
"""
|
530
|
-
command =
|
533
|
+
command = [get_sys_executable(), "-m", "prefect.engine"]
|
531
534
|
|
532
535
|
flow_run_logger = self._get_flow_run_logger(flow_run)
|
533
536
|
|
@@ -574,7 +577,7 @@ class Runner:
|
|
574
577
|
setattr(storage, "last_adhoc_pull", datetime.datetime.now())
|
575
578
|
|
576
579
|
process = await run_process(
|
577
|
-
|
580
|
+
command=command,
|
578
581
|
stream_output=True,
|
579
582
|
task_status=task_status,
|
580
583
|
env=env,
|
prefect/settings.py
CHANGED
@@ -425,13 +425,20 @@ def default_database_connection_url(settings: "Settings", value: Optional[str]):
|
|
425
425
|
f"Missing required database connection settings: {', '.join(missing)}"
|
426
426
|
)
|
427
427
|
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
428
|
+
# We only need SQLAlchemy here if we're parsing a remote database connection
|
429
|
+
# string. Import it here so that we don't require the prefect-client package
|
430
|
+
# to have SQLAlchemy installed.
|
431
|
+
from sqlalchemy import URL
|
432
|
+
|
433
|
+
return URL(
|
434
|
+
drivername=driver,
|
435
|
+
host=PREFECT_API_DATABASE_HOST.value_from(settings),
|
436
|
+
port=PREFECT_API_DATABASE_PORT.value_from(settings) or 5432,
|
437
|
+
username=PREFECT_API_DATABASE_USER.value_from(settings),
|
438
|
+
password=PREFECT_API_DATABASE_PASSWORD.value_from(settings),
|
439
|
+
database=PREFECT_API_DATABASE_NAME.value_from(settings),
|
440
|
+
query=[],
|
441
|
+
).render_as_string(hide_password=False)
|
435
442
|
|
436
443
|
elif driver == "sqlite+aiosqlite":
|
437
444
|
path = PREFECT_API_DATABASE_NAME.value_from(settings)
|
@@ -1379,14 +1386,6 @@ PREFECT_API_MAX_FLOW_RUN_GRAPH_ARTIFACTS = Setting(int, default=10000)
|
|
1379
1386
|
The maximum number of artifacts to show on a flow run graph on the v2 API
|
1380
1387
|
"""
|
1381
1388
|
|
1382
|
-
|
1383
|
-
PREFECT_EXPERIMENTAL_ENABLE_CLIENT_SIDE_TASK_ORCHESTRATION = Setting(
|
1384
|
-
bool, default=False
|
1385
|
-
)
|
1386
|
-
"""
|
1387
|
-
Whether or not to enable experimental client side task run orchestration.
|
1388
|
-
"""
|
1389
|
-
|
1390
1389
|
# Prefect Events feature flags
|
1391
1390
|
|
1392
1391
|
PREFECT_RUNNER_PROCESS_LIMIT = Setting(int, default=5)
|