1313import datetime
1414import hashlib
1515import logging
16+ import uuid
1617from collections .abc import Callable , Collection , Iterator , Sequence
1718from typing import TYPE_CHECKING , Any , Literal
1819
@@ -418,7 +419,7 @@ def _write_invocation_row(
418419 executed_at = datetime .datetime .now (datetime .timezone .utc )
419420 executed_at_str = executed_at .isoformat ()
420421 record_id_src = (
421- f"{ fip_hash_str } ::{ pod_content_hash_str } ::{ run_id } ::{ status } ::{ executed_at_str } "
422+ f"{ fip_hash_str } ::{ pod_content_hash_str } ::{ run_id } ::{ status } ::{ executed_at_str } :: { uuid . uuid4 () } "
422423 ).encode ("utf-8" )
423424 record_id = hashlib .sha256 (record_id_src ).digest ()
424425
@@ -711,20 +712,16 @@ def _merge_config(
711712
712713
713714# ---------------------------------------------------------------------------
714- # SideEffectJobNode
715+ # SideEffectNode — lightweight blueprint node (no DB)
715716# ---------------------------------------------------------------------------
716717
717718
718- class SideEffectJobNode (StreamBase ):
719- """DB-backed execution node for side-effect pods.
720-
721- Created at pipeline compile time by ``PipelineJob``. Receives a
722- ``pipeline_database`` via ``attach_databases()``. ``run_id`` is passed
723- as a call-time keyword argument from the orchestrator.
719+ class SideEffectNode (StreamBase ):
720+ """Lightweight blueprint node for side-effect pods.
724721
725- Inherits from ``StreamBase `` for identity infrastructure and to satisfy
726- the ``producer`` / ``upstreams`` / ``output_schema`` contract required
727- by ``SyncPipelineOrchestrator._materialize_as_stream`` .
722+ Used by ``Pipeline `` (the blueprint) to represent a side-effect pod
723+ invocation without any DB attachment or execution logic. Analogous to
724+ ``FunctionNode`` in the function pod hierarchy .
728725
729726 Args:
730727 side_effect_pod: The ``SideEffectPod`` this node wraps.
@@ -743,8 +740,6 @@ def __init__(
743740 self ._pod = side_effect_pod
744741 self ._input_stream = input_stream
745742 super ().__init__ (label = label )
746- self ._pipeline_database : ArrowDatabaseProtocol | None = None
747- self ._table_path : tuple [str , ...] | None = None
748743
749744 # ------------------------------------------------------------------
750745 # StreamBase interface
@@ -808,14 +803,7 @@ def as_table(
808803 columns : ColumnConfig | dict [str , Any ] | None = None ,
809804 all_info : bool = False ,
810805 ) -> pa .Table :
811- """Collect all rows from ``iter_data()`` into an Arrow table.
812-
813- Warning:
814- Calling ``as_table()`` on a ``SideEffectJobNode`` iterates via
815- ``iter_data()``, which re-invokes the side-effect function for each
816- row with no DB logging and no ``run_id``. Use ``execute()`` for
817- orchestrated execution.
818- """
806+ """Collect all rows from ``iter_data()`` into an Arrow table."""
819807 from orcapod .types import ColumnConfig as _ColumnConfig
820808 from orcapod .utils import arrow_utils
821809
@@ -832,6 +820,47 @@ def as_table(
832820 pa .concat_tables (data_tables ),
833821 )
834822
823+ @property
824+ def node_uri (self ) -> tuple [str , ...]:
825+ """Canonical URI tuple identifying this side-effect node.
826+
827+ Returns:
828+ A tuple of the form ``("side_effect", label, content_hash_string)``.
829+ """
830+ return ("side_effect" , self ._pod .label , self ._pod .content_hash ().to_string ())
831+
832+
833+ # ---------------------------------------------------------------------------
834+ # SideEffectJobNode — DB-backed execution node
835+ # ---------------------------------------------------------------------------
836+
837+
838+ class SideEffectJobNode (SideEffectNode ):
839+ """DB-backed execution node for side-effect pods.
840+
841+ Created at pipeline compile time by ``PipelineJob``. Receives a
842+ ``pipeline_database`` via ``attach_databases()``. ``run_id`` is passed
843+ as a call-time keyword argument from the orchestrator.
844+
845+ Extends ``SideEffectNode`` with DB attachment and orchestrated execution
846+ methods. Analogous to ``FunctionJobNode`` in the function pod hierarchy.
847+
848+ Args:
849+ side_effect_pod: The ``SideEffectPod`` this node wraps.
850+ input_stream: The upstream stream at compile time.
851+ label: Optional display label.
852+ """
853+
854+ def __init__ (
855+ self ,
856+ side_effect_pod : SideEffectPod ,
857+ input_stream : StreamProtocol ,
858+ label : str | None = None ,
859+ ) -> None :
860+ super ().__init__ (side_effect_pod = side_effect_pod , input_stream = input_stream , label = label )
861+ self ._pipeline_database : ArrowDatabaseProtocol | None = None
862+ self ._table_path : tuple [str , ...] | None = None
863+
835864 # ------------------------------------------------------------------
836865 # DB attachment
837866 # ------------------------------------------------------------------
0 commit comments