from __future__ import annotations from dataclasses import dataclass from sapcli.exceptions import InvalidNameError @dataclass(frozen=True) class ObjectTypeConfig: key: str label: str obj_uri_template: str src_uri_template: str | None collection_uri: str create_content_type: str has_source: bool = True # 存在性检查专用端点模板;为 None 时回退到 obj_uri_template。 # 个别类型(如 tabletype)的 obj_uri 是 VIT 端点,对任意名称都返回 200, # 无法区分存在/不存在,因此存在性检查需走标准 DDIC 端点。 exists_uri_template: str | None = None def format_obj_uri(self, name: str, **kwargs: str) -> str: return self.obj_uri_template.format(name=name, **kwargs) def format_src_uri(self, name: str, **kwargs: str) -> str | None: if self.src_uri_template is None: return None return self.src_uri_template.format(name=name, **kwargs) def format_exists_uri(self, name: str, **kwargs: str) -> str: """格式化存在性检查端点。无专用模板时回退到 obj_uri_template。""" template = self.exists_uri_template or self.obj_uri_template return template.format(name=name, **kwargs) def format_collection_uri(self, **kwargs: str) -> str: return self.collection_uri.format(**kwargs) _OBJECT_TYPE_REGISTRY: dict[str, ObjectTypeConfig] = {} def _register(cfg: ObjectTypeConfig) -> ObjectTypeConfig: _OBJECT_TYPE_REGISTRY[cfg.key] = cfg return cfg REPORT = _register(ObjectTypeConfig( key="report", label="程序(REPORT)", obj_uri_template="/sap/bc/adt/programs/programs/{name}", src_uri_template="/sap/bc/adt/programs/programs/{name}/source/main", collection_uri="/sap/bc/adt/programs/programs", create_content_type="application/vnd.sap.adt.programs.programs.v2+xml", )) CLASS = _register(ObjectTypeConfig( key="class", label="类(CLASS)", obj_uri_template="/sap/bc/adt/oo/classes/{name}", src_uri_template="/sap/bc/adt/oo/classes/{name}/source/main", collection_uri="/sap/bc/adt/oo/classes", create_content_type="application/vnd.sap.adt.oo.classes.v1+xml", )) FUNCTION = _register(ObjectTypeConfig( key="function", label="函数模块(FUNCTION)", obj_uri_template="/sap/bc/adt/functions/groups/{group}/fmodules/{name}", src_uri_template="/sap/bc/adt/functions/groups/{group}/fmodules/{name}/source/main", collection_uri="/sap/bc/adt/functions/groups/{group}/fmodules", create_content_type="application/vnd.sap.adt.functions.fmodules.v1+xml", )) FUNCTIONGROUP = _register(ObjectTypeConfig( key="functiongroup", label="函数组(FUNCTION GROUP)", obj_uri_template="/sap/bc/adt/functions/groups/{name}", src_uri_template=None, collection_uri="/sap/bc/adt/functions/groups", create_content_type="application/vnd.sap.adt.functions.groups.v2+xml", has_source=False, )) INTERFACE = _register(ObjectTypeConfig( key="interface", label="接口(INTERFACE)", obj_uri_template="/sap/bc/adt/oo/interfaces/{name}", src_uri_template="/sap/bc/adt/oo/interfaces/{name}/source/main", collection_uri="/sap/bc/adt/oo/interfaces", create_content_type="application/vnd.sap.adt.oo.interfaces.v1+xml", )) DOMAIN = _register(ObjectTypeConfig( key="domain", label="域(DOMAIN)", obj_uri_template="/sap/bc/adt/ddic/domains/{name}", src_uri_template="/sap/bc/adt/ddic/domains/{name}/source/main", collection_uri="/sap/bc/adt/ddic/domains", create_content_type="application/vnd.sap.adt.domains.v2+xml", )) DATAELEMENT = _register(ObjectTypeConfig( key="dataelement", label="数据元素(DATA ELEMENT)", obj_uri_template="/sap/bc/adt/ddic/dataelements/{name}", src_uri_template="/sap/bc/adt/ddic/dataelements/{name}/source/main", collection_uri="/sap/bc/adt/ddic/dataelements", create_content_type="application/vnd.sap.adt.dataelements.v2+xml", )) TABLE = _register(ObjectTypeConfig( key="table", label="透明表(TABLE)", obj_uri_template="/sap/bc/adt/ddic/tables/{name}", src_uri_template="/sap/bc/adt/ddic/tables/{name}/source/main", collection_uri="/sap/bc/adt/ddic/tables", create_content_type="application/vnd.sap.adt.ddic.tables.v1+xml", )) STRUCTURE = _register(ObjectTypeConfig( key="structure", label="结构(STRUCTURE)", obj_uri_template="/sap/bc/adt/ddic/structures/{name}", src_uri_template="/sap/bc/adt/ddic/structures/{name}/source/main", collection_uri="/sap/bc/adt/ddic/structures", create_content_type="application/vnd.sap.adt.ddic.structures.v1+xml", )) TABLETYPE = _register(ObjectTypeConfig( key="tabletype", label="表类型(TABLE TYPE)", obj_uri_template="/sap/bc/adt/vit/wb/object_type/ttypda/object_name/{name}", src_uri_template=None, collection_uri="/sap/bc/adt/ddic/tabletypes", create_content_type="application/vnd.sap.adt.ddic.tabletypes.v1+xml", has_source=False, # VIT obj_uri 对任意名称都返回 200,存在性检查必须走 DDIC 端点 exists_uri_template="/sap/bc/adt/ddic/tabletypes/{name}", )) INCLUDE = _register(ObjectTypeConfig( key="include", label="Include程序(INCLUDE)", obj_uri_template="/sap/bc/adt/programs/includes/{name}", src_uri_template="/sap/bc/adt/programs/includes/{name}/source/main", collection_uri="/sap/bc/adt/programs/includes", create_content_type="application/vnd.sap.adt.programs.includes.v2+xml", )) CDSVIEW = _register(ObjectTypeConfig( key="cdsview", label="CDS视图(CDS VIEW)", # 与 commands/cds.py 和 client/_ddic.py 保持一致:/ddic/ddlsources(NW 7.40 验证可用) obj_uri_template="/sap/bc/adt/ddic/ddlsources/{name}", src_uri_template="/sap/bc/adt/ddic/ddlsources/{name}/source/main", collection_uri="/sap/bc/adt/ddic/ddlsources", create_content_type="text/plain", )) DCL = _register(ObjectTypeConfig( key="dcl", label="访问控制(DCL)", # CDS Access Control,端点 /ddic/dclsources(参考 docs/dev/adt-endpoint-reference.md) obj_uri_template="/sap/bc/adt/ddic/dclsources/{name}", src_uri_template="/sap/bc/adt/ddic/dclsources/{name}/source/main", collection_uri="/sap/bc/adt/ddic/dclsources", create_content_type="text/plain", )) DDLX = _register(ObjectTypeConfig( key="ddlX", label="元数据扩展(DDLX)", # Metadata Extension,端点 /ddic/metadataextensions(参考 docs/dev/adt-endpoint-reference.md) obj_uri_template="/sap/bc/adt/ddic/metadataextensions/{name}", src_uri_template="/sap/bc/adt/ddic/metadataextensions/{name}/source/main", collection_uri="/sap/bc/adt/ddic/metadataextensions", create_content_type="text/plain", )) MESSAGECLASS = _register(ObjectTypeConfig( key="messageclass", label="消息类(MESSAGE CLASS)", obj_uri_template="/sap/bc/adt/oo/t100/messages/classes/{name}", src_uri_template=None, collection_uri="/sap/bc/adt/oo/t100/messages/classes", create_content_type="application/vnd.sap.adt.t100.message.classes.v1+xml", has_source=False, )) VIEW = _register(ObjectTypeConfig( key="view", label="数据库视图(VIEW)", obj_uri_template="/sap/bc/adt/ddic/views/{name}", src_uri_template="/sap/bc/adt/ddic/views/{name}/source/main", collection_uri="/sap/bc/adt/ddic/views", create_content_type="application/vnd.sap.adt.ddic.views.v1+xml", )) SEARCHHELP = _register(ObjectTypeConfig( key="searchhelp", label="搜索帮助(SEARCH HELP)", obj_uri_template="/sap/bc/adt/ddic/searchhelps/{name}", src_uri_template=None, collection_uri="/sap/bc/adt/ddic/searchhelps", create_content_type="application/vnd.sap.adt.ddic.searchhelps.v1+xml", has_source=False, )) LOCKOBJECT = _register(ObjectTypeConfig( key="lockobject", label="锁对象(LOCK OBJECT)", obj_uri_template="/sap/bc/adt/ddic/lockobjects/{name}", src_uri_template=None, collection_uri="/sap/bc/adt/ddic/lockobjects", create_content_type="application/vnd.sap.adt.ddic.lockobjects.v1+xml", has_source=False, )) def get_type_config(key: str) -> ObjectTypeConfig: if key not in _OBJECT_TYPE_REGISTRY: raise ValueError(f"不支持的对象类型: {key}") return _OBJECT_TYPE_REGISTRY[key] def all_type_keys() -> list[str]: return list(_OBJECT_TYPE_REGISTRY.keys()) # sap-cli 类型 → SAP 传输系统 R3TR 对象代码(用于查 E071 表)。 # None 表示该类型非独立 R3TR 对象,查 E071 无意义。 _R3TR_OBJECT_CODE: dict[str, str | None] = { "report": "PROG", "class": "CLAS", "interface": "INTF", "function": None, # 函数模块属函数组(FUGR),非独立 R3TR "functiongroup": "FUGR", "domain": "DOMA", "dataelement": "DTEL", "table": "TABL", "structure": "TABL", "tabletype": "TTYP", "include": None, # Include 属所属程序,非独立 R3TR "cdsview": "DDLS", "messageclass": "MSAG", "view": "VIEW", "searchhelp": "SHLP", "lockobject": "ENQU", "dcl": "DCLS", "ddlX": "DDLX", } def get_r3tr_object_code(key: str) -> str | None: """返回对象类型对应的 SAP R3TR 传输对象代码;无独立代码返回 None。""" return _R3TR_OBJECT_CODE.get(key) @dataclass(frozen=True) class ParsedName: obj_uri: str src_uri: str | None display_name: str file_base: str # 存在性检查专用端点;默认与 obj_uri 相同。tabletype 等类型的 obj_uri # 不能用于存在性检查,需走标准 DDIC 端点。 exists_uri: str = "" def parse_object_name(name: str, obj_type: str) -> ParsedName: cfg = get_type_config(obj_type) if obj_type == "function": if "/" not in name: raise InvalidNameError( "function 类型需要'函数组名/函数模块名' 格式,例如: ZGROUP/Z_MY_FUNC" ) group, fm = name.split("/", 1) group = group.strip() fm = fm.strip() if not group or not fm: raise InvalidNameError("函数组名和函数模块名不能为空") return ParsedName( obj_uri=cfg.format_obj_uri(fm.lower(), group=group.lower()), src_uri=cfg.format_src_uri(fm.lower(), group=group.lower()), display_name=name, file_base=fm.lower(), exists_uri=cfg.format_exists_uri(fm.lower(), group=group.lower()), ) # VIT 端点需要大写名称以返回完整元数据;去掉前导 / # 但 VIT 对存在性检查无效(任意名称都返回 200),exists_uri 走 DDIC 端点(小写名) if obj_type == "tabletype": clean = name.lstrip("/") name_upper = clean.upper() return ParsedName( obj_uri=cfg.format_obj_uri(name_upper), src_uri=cfg.format_src_uri(name_upper), display_name=clean, file_base=clean.lower(), exists_uri=cfg.format_exists_uri(clean.lower()), ) name_lower = name.lower() return ParsedName( obj_uri=cfg.format_obj_uri(name_lower), src_uri=cfg.format_src_uri(name_lower), display_name=name, file_base=name_lower, exists_uri=cfg.format_exists_uri(name_lower), )