Python 88.3%
TypeScript 7.6%
Shell 4.1%
1#!/usr/bin/env python32"""Working helper (tmp, not shipped): query the OpenAI OpenAPI spec offline.34 dump.py list REGEX -> schema names matching regex5 dump.py schema NAME [DEPTH] -> resolved schema as JSON (refs inlined to DEPTH)6 dump.py flatten NAME [DEPTH] -> dotted-path parameter rows (JSON list)7 dump.py events UNION_NAME -> compact list of event schemas from a oneOf/anyOf union8 dump.py paths PATH [PATH...] -> operations for these paths (params, body ref, response ref)9"""10from __future__ import annotations11import json, re, sys12from pathlib import Path1314ROOT = Path('/Users/simon-pierreboucher/Desktop/doc-api')15SPEC = ROOT / 'sources/openai/openapi/openapi-master.yaml'16CACHE = ROOT / 'tmp/openai-core/spec.json'171819def load():20 if CACHE.exists():21 return json.loads(CACHE.read_text())22 import yaml23 try:24 from yaml import CSafeLoader as L25 except ImportError:26 from yaml import SafeLoader as L27 spec = yaml.load(SPEC.read_text(), Loader=L)28 CACHE.parent.mkdir(parents=True, exist_ok=True)29 CACHE.write_text(json.dumps(spec, default=str))30 return json.loads(CACHE.read_text())313233SPEC_OBJ = load()34SCHEMAS = SPEC_OBJ['components']['schemas']353637def ref_name(node):38 return node['$ref'].split('/')[-1] if isinstance(node, dict) and '$ref' in node else None394041def deref(node):42 """Return (resolved_node, ref_name). Sibling keys of a $ref (description etc.) override."""43 name = None44 while isinstance(node, dict) and '$ref' in node:45 name = ref_name(node)46 target = SCHEMAS.get(name)47 if target is None:48 return {'x-missing-ref': name}, name49 merged = dict(target)50 for k, v in node.items():51 if k != '$ref':52 merged[k] = v53 node = merged54 return node, name555657def clean(desc):58 if not desc:59 return None60 d = re.sub(r'\s+', ' ', str(desc)).strip()61 return d[:900]626364def split_nullable(node):65 """anyOf/oneOf with a {"type":"null"} member -> (other_members, nullable)"""66 for key in ('anyOf', 'oneOf'):67 if key in node and isinstance(node[key], list):68 members = [m for m in node[key] if not (isinstance(m, dict) and m.get('type') == 'null')]69 nullable = len(members) != len(node[key])70 return key, members, nullable71 return None, None, bool(node.get('nullable'))727374def merge_allof(node):75 if 'allOf' not in node:76 return node77 out = {k: v for k, v in node.items() if k != 'allOf'}78 props, req = {}, []79 for m in node['allOf']:80 r, _ = deref(m)81 r = merge_allof(r)82 props.update(r.get('properties', {}))83 req += r.get('required', [])84 for k in ('description', 'title'):85 if k in r and k not in out:86 out[k] = r[k]87 out.setdefault('type', 'object')88 out['properties'] = {**props, **out.get('properties', {})}89 out['required'] = sorted(set(req + out.get('required', [])))90 return out919293def type_label(node):94 if isinstance(node, bool):95 return 'any' if node else 'never'96 if not isinstance(node, dict):97 return 'any'98 node, name = deref(node)99 node = merge_allof(node)100 key, members, nullable = split_nullable(node)101 if members is not None:102 if len(members) == 1:103 t = type_label(members[0])104 else:105 labels = []106 for m in members:107 r, n = deref(m)108 r = merge_allof(r)109 if r.get('type') == 'object' and 'type' in r.get('properties', {}):110 e = r['properties']['type'].get('enum') or []111 labels.append(f"object<{e[0]}>" if len(e) == 1 else (n or 'object'))112 else:113 labels.append(type_label(m))114 t = ' | '.join(dict.fromkeys(labels))115 return t + (' | null' if nullable else '')116 t = node.get('type')117 if t == 'array':118 return f"array<{type_label(node.get('items', {}))}>"119 if t is None:120 return name or 'any'121 if t == 'object' and node.get('additionalProperties') and not node.get('properties'):122 return 'map<string,' + type_label(node['additionalProperties']) + '>'123 if nullable:124 t = f"{t} | null"125 return t126127128def const_type(node):129 p = node.get('properties', {})130 if 'type' in p:131 e = p['type'].get('enum')132 if e and len(e) == 1:133 return e[0]134 return None135136137def flatten(node, path, required, out, depth, stack, variant=None):138 if depth < 0:139 return140 node, name = deref(node)141 if name and name in stack:142 return143 stack = stack + ([name] if name else [])144 node = merge_allof(node)145 key, members, nullable = split_nullable(node)146 if members is not None and len(members) == 1:147 inner, _ = deref(members[0])148 inner = merge_allof(inner)149 merged = {**inner, **{k: v for k, v in node.items() if k not in (key,)}}150 if nullable:151 merged['nullable'] = True152 node = merged153 key, members, nullable = split_nullable(node)154 row = {155 'parameter': path,156 'type': type_label(node),157 'required': required,158 'default': node.get('default'),159 'minimum': node.get('minimum'),160 'maximum': node.get('maximum'),161 'enum': node.get('enum'),162 'description': clean(node.get('description')),163 'deprecated': node.get('deprecated', False),164 'nullable': nullable or bool(node.get('nullable')),165 'variant': variant,166 'schema_ref': name,167 }168 if path:169 out.append(row)170 if members is not None and len(members) > 1:171 for m in members:172 r, n = deref(m)173 r = merge_allof(r)174 ct = const_type(r)175 if r.get('type') == 'object' or 'properties' in r:176 sub = f"{path}({ct or n or 'object'})"177 flatten(m, sub, required, out, depth - 1, stack, variant=ct or n)178 elif r.get('type') == 'array':179 flatten(m, path + '[]', False, out, depth - 1, stack)180 return181 if node.get('type') == 'array' or 'items' in node:182 flatten(node.get('items', {}), path + '[]', False, out, depth - 1, stack)183 return184 props = node.get('properties') or {}185 req = set(node.get('required', []))186 for pname, pnode in props.items():187 flatten(pnode, f"{path}.{pname}" if path else pname, pname in req, out, depth - 1, stack)188189190def show_schema(name, depth=3):191 def walk(node, d):192 node, n = deref(node)193 if d <= 0:194 return {'$schema': n} if n else {k: v for k, v in node.items() if k in ('type', 'enum')}195 if isinstance(node, dict):196 return {k: (walk(v, d - 1) if k in ('properties', 'items', 'allOf', 'anyOf', 'oneOf', 'additionalProperties') or isinstance(v, (dict, list)) and k not in ('enum', 'required', 'example', 'x-oaiMeta') else v)197 for k, v in node.items()}198 if isinstance(node, list):199 return [walk(x, d) for x in node]200 return node201 return walk(SCHEMAS[name], depth)202203204def events(union):205 node = SCHEMAS[union]206 members = node.get('anyOf') or node.get('oneOf') or []207 out = []208 for m in members:209 r, n = deref(m)210 r = merge_allof(r)211 ct = const_type(r)212 props = {}213 for pn, pv in (r.get('properties') or {}).items():214 props[pn] = {'type': type_label(pv), 'description': clean(deref(pv)[0].get('description'))}215 out.append({'schema': n, 'type': ct, 'description': clean(r.get('description')),216 'properties': props, 'required': r.get('required', []),217 'example': (r.get('x-oaiMeta') or {}).get('example')})218 return out219220221def paths(plist):222 out = {}223 for p in plist:224 item = SPEC_OBJ['paths'].get(p)225 if not item:226 out[p] = 'MISSING'227 continue228 for method, op in item.items():229 if method not in ('get', 'post', 'delete', 'patch', 'put'):230 continue231 params = []232 for prm in op.get('parameters', []):233 prm, _ = deref(prm) if '$ref' in prm else (prm, None)234 sch = prm.get('schema', {})235 params.append({'name': prm.get('name'), 'in': prm.get('in'), 'required': prm.get('required', False),236 'type': type_label(sch), 'enum': deref(sch)[0].get('enum'), 'default': deref(sch)[0].get('default'),237 'description': clean(prm.get('description'))})238 body = op.get('requestBody', {}).get('content', {})239 body_ref = {ct: ref_name(v.get('schema', {})) or type_label(v.get('schema', {})) for ct, v in body.items()}240 resp = {}241 for code, r in op.get('responses', {}).items():242 for ct, v in (r.get('content') or {}).items():243 resp[f"{code} {ct}"] = ref_name(v.get('schema', {})) or type_label(v.get('schema', {}))244 meta = op.get('x-oaiMeta', {})245 out[f"{method.upper()} {p}"] = {'operationId': op.get('operationId'), 'summary': clean(op.get('summary')),246 'description': clean(op.get('description')), 'parameters': params,247 'requestBody': body_ref, 'responses': resp, 'deprecated': op.get('deprecated', False),248 'meta': {k: (clean(v) if isinstance(v, str) else v) for k, v in meta.items() if k in ('name', 'group', 'returns', 'beta', 'path')}}249 return out250251252if __name__ == '__main__':253 cmd = sys.argv[1]254 if cmd == 'list':255 rx = re.compile(sys.argv[2])256 print('\n'.join(n for n in SCHEMAS if rx.search(n)))257 elif cmd == 'schema':258 print(json.dumps(show_schema(sys.argv[2], int(sys.argv[3]) if len(sys.argv) > 3 else 3), indent=1))259 elif cmd == 'flatten':260 rows = []261 flatten({'$ref': f'#/components/schemas/{sys.argv[2]}'}, '', False, rows, int(sys.argv[3]) if len(sys.argv) > 3 else 6, [])262 print(json.dumps(rows, indent=None))263 elif cmd == 'events':264 print(json.dumps(events(sys.argv[2]), indent=1))265 elif cmd == 'paths':266 print(json.dumps(paths(sys.argv[2:]), indent=1))267