Files
WuRangyu 02bc2c5165 v2.3.0: info 新增开发包/传输请求字段 (纯只读 E071)
- info 输出新增「开发包」(packageRef)、「传输请求」(纯只读查 E071)

- 优雅降级: 无请求/查询失败/非独立传输对象, 不中断 info

- bump 2.2.1 -> 2.3.0

- pack 从源码同步 (含 parser/transport 最新代码)
2026-08-06 11:57:36 +08:00

312 lines
11 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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/ddlsourcesNW 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),
)