Files
sap-cli-skill/assets/sapcli/types.py
T
wurangyu 38e99638c5 chore: sync skill to v2.2.1
- 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
2026-07-10 19:13:46 +08:00

283 lines
10 KiB
Python
Raw 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())
@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),
)