forked from OpenBMB/ChatDev
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathschema_exporter.py
More file actions
executable file
·139 lines (112 loc) · 4.67 KB
/
schema_exporter.py
File metadata and controls
executable file
·139 lines (112 loc) · 4.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
"""Schema exporter for dynamic configuration metadata."""
import hashlib
import json
from dataclasses import dataclass
from typing import Any, Dict, Iterable, List, Mapping, Sequence, Type
from entity.configs import BaseConfig
from entity.configs.graph import DesignConfig
SCHEMA_VERSION = "0.1.0"
class SchemaResolutionError(ValueError):
"""Raised when breadcrumbs fail to resolve to a config node."""
@dataclass(frozen=True)
class Breadcrumb:
"""Describes one hop in the config tree."""
node: str
field: str | None = None
value: Any | None = None
@classmethod
def from_mapping(cls, data: Mapping[str, Any]) -> "Breadcrumb":
node = str(data.get("node")) if data.get("node") else ""
if not node:
raise SchemaResolutionError("breadcrumb entry missing 'node'")
field = data.get("field")
if field is not None:
field = str(field)
index = data.get("index")
if index is not None and not isinstance(index, int):
raise SchemaResolutionError("breadcrumb 'index' must be integer when provided")
value = data.get("value")
return cls(node=node, field=field, value=value)
def to_json(self) -> Dict[str, Any]:
payload: Dict[str, Any] = {"node": self.node}
if self.field is not None:
payload["field"] = self.field
if self.value is not None:
payload["value"] = self.value
return payload
def _normalize_breadcrumbs(raw: Sequence[Mapping[str, Any]] | None) -> List[Breadcrumb]:
if not raw:
return []
return [Breadcrumb.from_mapping(item) for item in raw]
def _resolve_config_class(
breadcrumbs: Sequence[Breadcrumb],
*,
root_cls: Type[BaseConfig] = DesignConfig,
) -> Type[BaseConfig]:
current_cls: Type[BaseConfig] = root_cls
for crumb in breadcrumbs:
if crumb.node != current_cls.__name__:
raise SchemaResolutionError(
f"breadcrumb node '{crumb.node}' does not match current config '{current_cls.__name__}'"
)
if crumb.field is None:
continue
child_cls = current_cls.resolve_child(crumb.field, crumb.value)
if child_cls is None:
spec = current_cls.field_specs().get(crumb.field)
if not spec or spec.child is None:
raise SchemaResolutionError(
f"field '{crumb.field}' on {current_cls.__name__} is not navigable"
)
child_cls = spec.child
current_cls = child_cls
return current_cls
def _serialize_field(config_cls: Type[BaseConfig], name: str, spec_dict: Dict[str, Any]) -> Dict[str, Any]:
field_spec = spec_dict[name]
data = field_spec.to_json()
routes = [
{
"childKey": key.to_json(),
"childNode": target.__name__,
}
for key, target in config_cls.child_routes().items()
if key.field == name
]
if routes:
data["childRoutes"] = routes
return data
def _ordered_field_names(specs: Mapping[str, Any]) -> List[str]:
"""Return field names with required ones first while keeping relative order."""
items = list(specs.items())
required_names = [name for name, spec in items if getattr(spec, "required", False)]
optional_names = [name for name, spec in items if not getattr(spec, "required", False)]
return required_names + optional_names
def _hash_payload(payload: Dict[str, Any]) -> str:
serialized = json.dumps(payload, sort_keys=True, ensure_ascii=False, default=str)
return hashlib.sha1(serialized.encode("utf-8")).hexdigest()
def build_schema_response(
breadcrumbs_raw: Sequence[Mapping[str, Any]] | None = None,
*,
root_cls: Type[BaseConfig] = DesignConfig,
) -> Dict[str, Any]:
"""Return a JSON-serializable schema response for the provided breadcrumbs."""
breadcrumbs = _normalize_breadcrumbs(breadcrumbs_raw)
target_cls = _resolve_config_class(breadcrumbs, root_cls=root_cls)
schema_node = target_cls.collect_schema()
field_specs = target_cls.field_specs()
ordered_fields = _ordered_field_names(field_specs)
fields_payload = [_serialize_field(target_cls, name, field_specs) for name in ordered_fields]
response = {
"schemaVersion": SCHEMA_VERSION,
"node": schema_node.node,
"fields": fields_payload,
"constraints": [constraint.to_json() for constraint in schema_node.constraints],
"breadcrumbs": [crumb.to_json() for crumb in breadcrumbs],
}
response["cacheKey"] = _hash_payload({"node": schema_node.node, "breadcrumbs": response["breadcrumbs"]})
return response
__all__ = [
"Breadcrumb",
"SchemaResolutionError",
"build_schema_response",
]