- NW 7.40 E2E-verified compatibility matrix in SKILL.md - DDIC fixes: include namespace, tabletype exists check, DDIC delete lock Accept - New commands: clone, enhancement, history, unit-test - 665 tests, 96% coverage
283 lines
10 KiB
Python
283 lines
10 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())
|
||
|
||
|
||
@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),
|
||
)
|