File size: 1,439 Bytes
116524e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
"""Immutable step context — the single object that flows through every step."""

from __future__ import annotations

import dataclasses
from dataclasses import dataclass, field
from types import MappingProxyType
from typing import Any, Self


@dataclass(frozen=True)
class StepContext:
    """Frozen context object passed from step to step.



    The pipeline engine only requires ``sample`` and ``metadata``.  All

    domain-specific fields are added by subclassing — the engine never reads

    anything beyond these two fields.



    Consuming applications subclass ``StepContext`` to add named fields for

    concepts shared across their pipelines.  Integration-specific data goes

    in ``metadata`` to prevent field accumulation on the subclass.



    Steps never mutate the incoming context — they call ``.replace()`` to

    produce a new one.

    """

    sample: Any = None
    metadata: MappingProxyType = field(default_factory=lambda: MappingProxyType({}))

    def __post_init__(self) -> None:
        # Coerce plain dict → MappingProxyType so mutation is a hard runtime error
        if not isinstance(self.metadata, MappingProxyType):
            object.__setattr__(self, "metadata", MappingProxyType(self.metadata))

    def replace(self, **changes: Any) -> Self:
        """Return a new context with the given fields replaced."""
        return dataclasses.replace(self, **changes)