Chronaxis用户手册English
本页目录

chronaxis.sources.protocol

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""约定来源的描述、分批读取和关闭接口,并检查跨批承诺。23SourceAdapter接收ReadRequest并产生SignalBatch;BatchValidator根据4SourceDescription检查会话、投影、类型、单位、时间顺序、序号和进度。5本模块不打开来源、不排序文件,也不计算业务事件。67校验器保存各列最近时间组的序号、来源最近时间、watermark_ns进度和8结束状态。进度承诺之后不能再接收小于或等于该时间的更新;失败时不推进9已有状态。状态大小取决于信号数量和当前组规模,执行层须将容器计入预算。10"""1112# Mapping和Iterator表达公开协议,不接自由object占位。13from collections.abc import Iterator14# Protocol用于实际adapter结构契约,不进行全局注册或导入任务。15from typing import Protocol, runtime_checkable1617# 公共来源模型已校验单批shape/质量/时间,跨批承诺在这里核对。18from .models import ReadRequest, SignalBatch, SourceDescription192021class SourceClosedError(RuntimeError):22    """表示已经关闭的来源仍被请求描述或读取;重复close本身应安全。23    """242526class SourceReadError(RuntimeError):27    """表示来源读取或批次协议失败;具体适配器负责附加位置并保留原异常链。28    """293031@runtime_checkable32class SourceAdapter(Protocol):33    """约束已解码来源的公共接口,不管理实现的内部资源。3435    describe返回声明,iter_batches按明确请求传送更新,close由实现释放资源。36    静态描述不应推进日志输入或加载DBC。此协议不保存实例状态,具体适配器37    负责活动生成器、句柄和关闭标记;同一来源的全部文件结束才报告source_eof。38    """3940    def describe(self) -> SourceDescription:41        """返回来源描述与能力声明,不推进输入读取。4243        参数:44            无。由实现使用当前来源配置和已有元数据。45        返回:46            SourceDescription。未知观测端点和不可用能力保留明确状态。47        异常:48            SourceClosedError: 来源已关闭。49            描述模型校验或实现的元数据错误由具体适配器报告。50        """51        # Protocol只声明方法,实际实现必须返回typed描述。52        ...5354    def iter_batches(self, request: ReadRequest) -> Iterator[SignalBatch]:55        """按请求传送指定别名的已解码更新。5657        参数:58            request (ReadRequest): 会话、别名投影、分析域和每批总样本上限。59        返回:60            Iterator[SignalBatch]。允许空批和同时间组跨批,整个来源结束时报告结束标记。61        异常:62            SourceClosedError、SourceReadError或请求与协议校验错误,由具体实现报告。63        边界:64            请求范围不授权读取下推或改变旧链调用行为。65        """66        # 接口不包含额外解码回调或读取下推。67        ...6869    def close(self) -> None:70        """释放适配器持有的活动资源,保留用户输入文件。7172        参数:73            无。具体实现关闭自己的生成器和句柄。74        返回:75            None。重复关闭应安全。76        异常:77            清理失败由实现报告;已有读取错误时应保留原错误及清理证据。78        """79        # 所有实际实现负责各自已打开的资源。80        ...818283class BatchValidator:84    """为一次读取请求核对跨批身份、时间顺序和结束状态。8586    description和request保存本请求声明,catalog按别名索引字段契约。87    watermark_ns记录已完整传送的时间进度,last_time_ns记录最近真实样本。88    pending保存每列最近时间组的序号集合;进度越过该组后释放集合。89    ended为True后不能再接收任何批次,包括空批。accept先在局部副本检查,90    全部通过才替换状态,因此失败批次不会污染已有进度。91    """9293    def __init__(self, description: SourceDescription, request: ReadRequest) -> None:94        """为当前来源及请求建立独立的跨批校验状态。9596        参数:97            description (SourceDescription): 当前来源的字段、类型、单位和观测声明。98            request (ReadRequest): 本次会话、信号别名和批次上限。99        返回:100            None。保存字段目录,并初始化时间、序号和结束状态;不读取来源。101        异常:102            TypeError: 输入不是规定的模型类型。103            ValueError: 请求别名在来源描述中不存在。104        """105        # 输入类型先验,不能从样本反推配置。106        if not isinstance(description, SourceDescription) or not isinstance(request, ReadRequest):107            # 描述和请求必须使用规定的类型化模型。108            raise TypeError('BatchValidator需要typed描述/请求')109        # catalog按本次绑定别名索引字段,不继承全局来源对象。110        catalog = {item.name: item for item in description.fields}111        # 所有请求必须在已知字段中,能力未知不猜测。112        if any(name not in catalog for name in request.signal_names):113            # 失败指出来源和具体投影。114            raise ValueError(f'{description.name}:未知signal_names')115        # 当前模型与请求不可变,后续只存本次状态。116        self.description, self.request, self.catalog = description, request, catalog117        # watermark是完整组的进度,初始尚未承诺。118        self.watermark_ns = None119        # 最近真实样本时间确保批次整体不会倒退。120        self.last_time_ns = None121        # 每个列的最近时间组/序号集合,用于跨批重复检查。122        self.pending = {}123        # EOF后即使空批也不能继续读。124        self.ended = False125126    def accept(self, batch: SignalBatch) -> None:127        """检查一批的身份和进度,全部通过后才更新请求状态。128129        参数:130            batch (SignalBatch): 已通过单批结构检查的批次。131        返回:132            None。更新最近时间、未完成组序号、进度承诺和来源结束标记。133        异常:134            ValueError: 批次类型或归属错误、超过样本上限、列契约变化、时间回退、135                重复观测、违反进度承诺或观测终点冲突;结束后任何批次也被拒绝。136        副作用:137            只在全部检查成功后改变本校验器;输入批次不被修改。138        边界:139            样本时间等于旧watermark_ns也被拒绝,因为该时间组已经承诺完整。140            真实点可以等于观测终点;分析开始前的保持样本仍可早于请求起点。141        """142        # 已结束的来源或错误批次类型都不能推进校验状态。143        if not isinstance(batch, SignalBatch) or self.ended:144            # EOF不是普通空batch,不能跨请求复用状态。145            raise ValueError(f'{self.description.name}:错误批次或EOF后继续')146        # 会话/来源与初始化契约完全一致。147        if batch.source != self.description.name or batch.session_id != self.request.session_id:148            # 值相同也不能借用他源/他会话定位。149            raise ValueError(f'{self.description.name}:批次归属冲突')150        # 所有series样本总数限额,而不是仅数不同时间点。151        if batch.sample_count > self.request.batch_rows:152            # 大同时间组应合法分批,不能放行超限。153            raise ValueError(f'{batch.source}:超过batch_rows样本总数')154        # watermark可重复但不能回退或撤销为None。155        if self.watermark_ns is not None and (batch.watermark_ns is None or batch.watermark_ns < self.watermark_ns):156            # 承诺不能因空批或下一文件而reset。157            raise ValueError(f'{batch.source}:watermark回退')158        # 在局部副本校验,失败不把坏批写进执行状态。159        pending = {name: (time, set(sequences)) for name, (time, sequences) in self.pending.items()}160        # 最大真实时间按上一批保存,空批不会清空它。161        latest = self.last_time_ns162        # 各列分别验证dtype/单位/原定位与投影。163        for name, column in batch.series.items():164            # 公共请求不接受额外列,避免绑定泄漏。165            if name not in self.request.signal_names:166                # 未知列不是忽略后继续的普通数据。167                raise ValueError(f'{batch.source}:非请求列{name}')168            # 描述中已经校验字段实际归属。169            definition = self.catalog[name]170            # 输出类型和单位不能在batch间静默改变。171            if column.dtype is not definition.dtype or column.unit != definition.unit or column.labels != definition.metadata.get('labels', ()) or any(ref.field != definition.field.field for ref in column.source_ref):172                # 表字段与别名分开,不从semantic_id猜定位。173                raise ValueError(f'{batch.source}:列{name}契约/原定位变化')174            # 单批数组已经有序;本列首点不能早于上一批任意列的真实末点。175            if len(column.time_ns) and self.last_time_ns is not None and int(column.time_ns[0]) < self.last_time_ns:176                # 不重排解析结果消除来源内乱序。177                raise ValueError(f'{batch.source}:跨batch时间回退')178            # 已完整的时间组不能再收到同刻更新。179            if len(column.time_ns) and self.watermark_ns is not None and int(column.time_ns[0]) <= self.watermark_ns:180                # 恰好等于旧进度也被拒绝,因为已承诺该时间及更早的更新完整传送。181                raise ValueError(f'{batch.source}:样本不晚于已承诺watermark')182            # 观测边界若有明确事实,真实点可以等于end但不能越界。183            if len(column.time_ns) and ((self.description.observation_start_ns is not None and int(column.time_ns[0]) < self.description.observation_start_ns) or (self.description.observation_end_ns is not None and int(column.time_ns[-1]) > self.description.observation_end_ns)):184                # domain不同于观测事实,前置保持样本仍可在request.start之前。185                raise ValueError(f'{batch.source}:样本超出来源观测声明')186            # 逐样本序号检查用于协议身份去重,不进行数值计算。187            for time_value, sequence_value in zip(column.time_ns, column.sequence):188                # 只转精确整数,禁止大时间先转float。189                time, sequence = int(time_value), int(sequence_value)190                # 新时间组替换该列pending,不保留无界历史身份集合。191                previous_time, sequences = pending.get(name, (None, set()))192                # 列时间推进后旧组不可能合法回来。193                if previous_time != time:194                    # 当前组的去重集合仍须计入执行层预算。195                    sequences = set()196                # 跨batch重复同一观测必须失败,不重复计入事件/面积。197                if sequence in sequences:198                    # 不是正常同时间不同原行。199                    raise ValueError(f'{batch.source}:同组重复sequence/{name}')200                # 仅登记当前组序号,其他列同原行可以各自存在。201                sequences.add(sequence)202                # 每列pending独立,异步列不互相覆盖。203                pending[name] = (time, sequences)204                # 保存整来源最新真实末点。205                latest = time if latest is None else max(latest, time)206        # 真实末点不能超过已知来源观测终点,即使当前批为空。207        if batch.observed_end_ns is not None and latest is not None and batch.observed_end_ns < latest:208            # 不用更早的终点悄悄截断已传输数据。209            raise ValueError(f'{batch.source}:observed_end早于真实末点')210        # 静态已声明终点与EOF实际观测必须保持相同事实。211        if batch.source_eof and self.description.observation_end_ns is not None and batch.observed_end_ns != self.description.observation_end_ns:212            # unknown描述不能在这里反向提升为verified。213            raise ValueError(f'{batch.source}:EOF观测终点与声明冲突')214        # 完整组以后不能再到达,立即释放其身份集合,不保留历史全量。215        pending = {name: state for name, state in pending.items() if batch.watermark_ns is None or state[0] > batch.watermark_ns}216        # 检查全部成功后才原子替换协议状态。217        self.pending, self.last_time_ns = pending, latest218        # 不从样本最大时间猜watermark,只采用adapter明确承诺。219        self.watermark_ns = batch.watermark_ns220        # EOF保证之后不再接收任意更新/空批。221        self.ended = batch.source_eof222
Chronaxis 用户手册 · 离线阅读