Chronaxis用户手册English
本页目录

chronaxis.configuration.file_tools

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""当前配置的格式转换和新文件发布;不推断业务参数,不执行分析。"""2from io import StringIO3import json4import math5from pathlib import Path67from . import load_configuration, parse_configuration8from .diagnostics import locate_error91011def configuration_type(data):12    """识别当前分析或批量入口。参数: 原生配置。返回: analysis/batch。异常: ValueError。1314    只接受当前分析及批量格式,不猜测或迁移输入版本。15    """16    from ..planning.checks import PlanValidationError17    versions={'config_version':('analysis',(4,)),'batch_version':('batch',(1,))}18    present=[key for key in versions if type(data) is dict and key in data]19    if len(present)!=1:raise PlanValidationError('$','需要且只能有一种配置版本字段','invalid_version')20    key=present[0];name,version=versions[key]21    if type(data[key]) is not int or data[key] not in version:22        raise PlanValidationError('$.'+key,'仅接受当前版本'+'或'.join(map(str, version))+',不转换旧结构','invalid_version')23    return name242526def validate_document(document, base, *, return_plan=False):27    """调用现有模型;return_plan=True时同时返回计划,避免配置工具重复加载DBC。2829    参数: document为解析配置,base为相对路径基准,return_plan控制是否返回计划。30    返回: 入口类别,或(类别, 已校验计划)。31    异常: PlanValidationError附带文件位置;文件与依赖错误按原入口传播。32    批量不加载子任务;未知格式在读取业务数据前拒绝。33    """34    from ..planning import AnalysisPlan35    from ..planning.checks import PlanValidationError36    from .imports import expand_document_imports37    try:38        document=expand_document_imports(document)39        name=configuration_type(document.data)40        if name=='analysis':plan=AnalysisPlan.from_dict(document.data,base_dir=base)41        elif name=='batch':42            from ..batch.models import BatchPlan43            plan=BatchPlan.from_dict(document.data,base_dir=base)44        return (name,plan) if return_plan else name45    except PlanValidationError as error:raise locate_error(error,document)464748def path_fields(data, config_type):49    """枚举声明的文件路径位置。参数: 已校验配置及类型。返回: (容器,键)迭代器。异常: 内部结构错误。5051    只认来源文件/DBC、批量子计划和公共定义;metadata与表达式中的path文字保持原样。52    """53    if config_type=='batch':54        for job in data.get('jobs',[]):yield job,'plan'55        if 'template' in data:56            yield data,'template'57            from ..planning.input_paths import source_path_fields58            holder={'paths':data['inputs']}59            for owner,key in source_path_fields(holder):60                yield (data,'inputs') if owner is holder else (owner,key)61        return62    # 非批量入口已校验为当前分析配置,路径直接位于根级来源和公共定义中。63    for source in data.get('sources',[]):64        from ..planning.input_paths import source_path_fields65        yield from source_path_fields(source)66        for channel in source.get('dbc_by_channel',{}):yield source['dbc_by_channel'],channel67    for index in range(len(data.get('imports',[]))):yield data['imports'],index686970def same_native_value(left,right):71    """核类型/顺序/数值不漂移,包含负零和大整数。参数: 原生值。返回: bool。异常: 无。"""72    if type(left) is not type(right):return False73    if type(left) is dict:74        return list(left)==list(right) and all(same_native_value(left[key],right[key]) for key in left)75    if type(left) is list:76        return len(left)==len(right) and all(same_native_value(a,b) for a,b in zip(left,right))77    if type(left) is float and left==0:return left==right and math.copysign(1,left)==math.copysign(1,right)78    return left==right798081def serialize_configuration(data, format):82    """输出可被受限解析器再读取的文本。参数: 配置及json/yaml。返回: str。异常: ValueError。8384    输出后独立重解析并核类型和值,避免YAML隐式类型或浮点表示改写含义。注释不参与互转。85    """86    if format=='json':text=json.dumps(data,ensure_ascii=False,allow_nan=False,indent=2)+'\n'87    elif format=='yaml':88        from ruamel.yaml import YAML89        yaml=YAML(typ='safe',pure=True);yaml.version=(1,2);yaml.default_flow_style=False90        yaml.sort_base_mapping_type_on_output=False91        yaml.representer.ignore_aliases=lambda value:True92        stream=StringIO();yaml.dump(data,stream);text=stream.getvalue()93    else:raise ValueError('输出格式仅支持json或yaml')94    if not same_native_value(data,parse_configuration(text,format=format).data):95        raise ValueError('格式转换不能保持原配置类型、顺序和值;未写文件')96    return text979899def output_format(path):100    """由扩展名选择格式。参数: Path。返回: json/yaml。异常: ValueError。"""101    suffix=path.suffix.lower()102    if suffix not in ('.json','.yaml','.yml'):raise ValueError('新文件需要.json、.yaml或.yml扩展名')103    return 'json' if suffix=='.json' else 'yaml'104105106def write_new_configuration(path, text):107    """独占写新文件。参数: 路径及已校验文本。返回: Path。异常: IO/取消。108109    已存在文件、硬链接和符号链接都由独占打开拒绝;部分写失败仅清理本次创建的文件。110    """111    target=Path(path).absolute()112    stream=target.open('x',encoding='utf-8',newline='\n')113    try:114        with stream:stream.write(text);stream.flush()115    except BaseException as error:116        try:stream.close()117        except OSError as cleanup:error.add_note('未能关闭部分配置 '+str(target)+': '+str(cleanup))118        try:target.unlink()119        except OSError as cleanup:error.add_note('未能清理部分配置 '+str(target)+': '+str(cleanup))120        raise121    return target122123124def convert_configuration(source, output):125    """仅改变当前配置的JSON/YAML表示。参数: 输入、新文件。返回: 输出Path。异常: 配置/IO错误。126127    相对路径存在时只允许同目录输出;跨目录需使用resolve显式重定位,不静默修改原值。128    """129    source=Path(source).absolute();target=Path(output).absolute()130    document=load_configuration(source);name=validate_document(document,source.parent)131    if source.parent!=target.parent and any(not Path(owner[key]).is_absolute() for owner,key in path_fields(document.data,name)):132        raise ValueError('配置含相对路径,convert只能写入同目录;跨目录请用config resolve --output重新定位')133    text=serialize_configuration(document.data,output_format(target))134    return write_new_configuration(target,text)135
Chronaxis 用户手册 · 离线阅读