- info 输出新增「开发包」(packageRef)、「传输请求」(纯只读查 E071) - 优雅降级: 无请求/查询失败/非独立传输对象, 不中断 info - bump 2.2.1 -> 2.3.0 - pack 从源码同步 (含 parser/transport 最新代码)
312 lines
11 KiB
Python
312 lines
11 KiB
Python
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),
|
||
)
|