Files
WeHub Mirror 2bba424313
Integration Tests - PostgreSQL + Elasticsearch + Redis / Detect Changes (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / Detect Changes (push) Has been cancelled
Java Checkstyle / java-checkstyle (push) Has been cancelled
Maven Collate Tests / maven-collate-ci (push) Has been cancelled
OpenMetadata Service Unit Tests / Detect Changes (push) Has been cancelled
Publish Package to Maven Central Repository / publish-maven-packages (push) Has been cancelled
Integration Tests - MySQL + Elasticsearch / Detect Changes (push) Has been cancelled
Integration Tests - PostgreSQL + Elasticsearch + Redis / Build Integration Test Runtime (push) Has been cancelled
Integration Tests - PostgreSQL + Elasticsearch + Redis / Integration Test Lane (global-state) (push) Has been cancelled
Integration Tests - PostgreSQL + Elasticsearch + Redis / Integration Test Lane (multi-node) (push) Has been cancelled
Integration Tests - PostgreSQL + Elasticsearch + Redis / Integration Test Lane (parallel) (push) Has been cancelled
Integration Tests - PostgreSQL + Elasticsearch + Redis / Integration Test Lane (retry-queue) (push) Has been cancelled
Integration Tests - PostgreSQL + Elasticsearch + Redis / ${{ github.event_name == 'pull_request_target' && github.event.action == 'labeled' && github.event.label.name != 'safe to test' && 'integration-tests-postgres-elasticsearch-redis (ignored label)' || 'integration-tests-postgres-elasticsearch-redis' }} (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / Build Integration Test Runtime (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / Integration Test Lane (global-state) (push) Has been cancelled
Integration Tests - MySQL + Elasticsearch / Build Integration Test Runtime (push) Has been cancelled
Integration Tests - MySQL + Elasticsearch / Integration Test Lane (global-state) (push) Has been cancelled
Integration Tests - MySQL + Elasticsearch / Integration Test Lane (multi-node) (push) Has been cancelled
Integration Tests - MySQL + Elasticsearch / Integration Test Lane (parallel) (push) Has been cancelled
Integration Tests - MySQL + Elasticsearch / Integration Test Lane (retry-queue) (push) Has been cancelled
Integration Tests - MySQL + Elasticsearch / ${{ github.event_name == 'pull_request_target' && github.event.action == 'labeled' && github.event.label.name != 'safe to test' && 'integration-tests-mysql-elasticsearch (ignored label)' || 'integration-tests-mysql-elasticsearch' }} (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / Integration Test Lane (parallel) (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / Integration Test Lane (retry-queue) (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / ${{ github.event_name == 'pull_request_target' && github.event.action == 'labeled' && github.event.label.name != 'safe to test' && 'integration-tests-postgres-opensearch (ignored label)' || 'integration-tests-postgres-opensearch' }} (push) Has been cancelled
OpenMetadata Service Unit Tests / openmetadata-service-unit-tests (push) Has been cancelled
OpenMetadata Service Unit Tests / k8s_operator-unit-tests (push) Has been cancelled
OpenMetadata Service Unit Tests / openmetadata-service-unit-tests-status (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / Integration Test Lane (multi-node) (push) Has been cancelled
WeHub snapshot of bae3e29f60050c5970ef31185b76f560fdee41b7
2026-08-07 16:54:16 +08:00

176 lines
6.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""Generate docs/generated/api-reference.md — an index of REST endpoints.
The source is the JAX-RS resource classes under
``openmetadata-service/src/main/java/org/openmetadata/service/resources/**/*Resource.java``.
It is NOT ``openapi.yml`` — that file is a 300-byte SwaggerBundle config stub
(title / version / security scheme) with no endpoints; the full OpenAPI document is
assembled at runtime by Dropwizard from these annotations, and is not committed.
For each endpoint:
- HTTP method + path are EXACT — parsed from ``@GET``/``@POST``/``@PUT``/``@DELETE``/``@PATCH``
plus the class-level and method-level ``@Path``.
- purpose comes from ``@Operation(summary = "…")`` when present, and is left blank
otherwise. It is never inferred — a blank cell means "no summary annotation," not
"no purpose."
Endpoints are grouped by resource package (the directory under ``resources/``) and
sorted, so the output is deterministic (byte-identical across runs, no timestamps).
Do not edit by hand — run ``make generate-api-reference`` (or ``make generate-reference-docs``).
"""
import os
import re
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
RESOURCE_ROOT = "openmetadata-service/src/main/java/org/openmetadata/service/resources"
OUT_PATH = "docs/generated/api-reference.md"
HTTP_METHODS = ("GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS")
HTTP_ANNOTATION_RE = re.compile(r"^\s*@(" + "|".join(HTTP_METHODS) + r")\b")
PATH_RE = re.compile(r'@Path\(\s*"([^"]*)"\s*\)')
SUMMARY_RE = re.compile(r'summary\s*=\s*"((?:[^"\\]|\\.)*)"')
CLASS_DECL_RE = re.compile(r"\b(class|interface)\s+\w+")
METHOD_SIG_RE = re.compile(r"^(public|protected|private)\b")
def repo(*parts):
return os.path.join(REPO_ROOT, *parts)
def join_path(base, suffix):
combined = "/" + "/".join(part for part in (base + "/" + suffix).split("/") if part)
return combined if combined != "/" else "/"
def class_base_path(lines):
"""The @Path that annotates the class declaration (applies to every method)."""
base = ""
pending = ""
for line in lines:
path_match = PATH_RE.search(line)
if path_match is not None:
pending = path_match.group(1)
if CLASS_DECL_RE.search(line) is not None:
base = pending
break
return base
def annotation_block_above(lines, sig_index):
"""The contiguous annotation/JavaDoc lines directly above a method signature.
Stops at a blank line or a closing brace (the end of the previous member), so
multi-line ``@Operation(...)`` blocks are captured whole regardless of their
internal continuation lines.
"""
block = []
cursor = sig_index - 1
while cursor >= 0:
stripped = lines[cursor].strip()
if stripped == "" or stripped == "}":
break
block.append(lines[cursor])
cursor -= 1
block.reverse()
return block
def endpoint_from_block(block, base_path):
verb = None
for line in block:
verb_match = HTTP_ANNOTATION_RE.match(line)
if verb_match is not None:
verb = verb_match.group(1)
break
if verb is None:
return None
text = " ".join(block)
path_match = PATH_RE.search(text)
method_path = path_match.group(1) if path_match is not None else ""
summary_match = SUMMARY_RE.search(text)
summary = summary_match.group(1) if summary_match is not None else ""
return (verb, join_path(base_path, method_path), summary)
def parse_resource(full):
with open(full, encoding="utf-8") as handle:
lines = handle.read().splitlines()
base_path = class_base_path(lines)
endpoints = []
for index, line in enumerate(lines):
if METHOD_SIG_RE.match(line.strip()) is None or "(" not in line:
continue
block = annotation_block_above(lines, index)
endpoint = endpoint_from_block(block, base_path)
if endpoint is not None:
endpoints.append(endpoint)
return endpoints
def collect():
groups = {}
for dirpath, _dirs, files in os.walk(repo(RESOURCE_ROOT)):
for name in sorted(files):
if not name.endswith("Resource.java"):
continue
full = os.path.join(dirpath, name)
endpoints = parse_resource(full)
if not endpoints:
continue
package = os.path.relpath(dirpath, repo(RESOURCE_ROOT)).replace(os.sep, "/")
if package == ".":
package = "(root)"
groups.setdefault(package, []).extend(endpoints)
for package in groups:
groups[package] = sorted(set(groups[package]), key=lambda item: (item[1], item[0], item[2]))
return groups
def render(groups):
total = sum(len(items) for items in groups.values())
with_summary = sum(1 for items in groups.values() for item in items if item[2])
lines = [
"<!-- GENERATED FILE — DO NOT EDIT. Run `make generate-api-reference`. -->",
"",
"# API Reference (endpoint index)",
"",
"Every REST endpoint, grouped by resource package. **Generated** from the JAX-RS",
"resource classes under",
"`openmetadata-service/src/main/java/org/openmetadata/service/resources/**` — do not",
"hand-edit; run `make generate-api-reference` (or `make generate-reference-docs`).",
"",
"- **Method + path are exact** (from `@GET`/`@POST`/… and `@Path`).",
"- **Purpose** is the `@Operation(summary=…)` where present; a blank cell means there is",
" no summary annotation, not that the endpoint has no purpose. Purposes are never inferred.",
"- Source is the annotations, **not** `openapi.yml` (a config stub with no endpoints; the",
" full spec is assembled at runtime by Dropwizard).",
"",
f"**{total} endpoints** across {len(groups)} resource packages · {with_summary} carry a summary.",
"",
]
for package in sorted(groups):
lines.append(f"## {package}")
lines.append("")
lines.append("| Method | Path | Purpose |")
lines.append("|---|---|---|")
for verb, path, summary in groups[package]:
escaped = summary.replace("|", "\\|")
lines.append(f"| `{verb}` | `{path}` | {escaped} |")
lines.append("")
return "\n".join(lines).rstrip("\n") + "\n"
def main():
groups = collect()
output = render(groups)
os.makedirs(repo(os.path.dirname(OUT_PATH)), exist_ok=True)
with open(repo(OUT_PATH), "w", encoding="utf-8") as handle:
handle.write(output)
total = sum(len(items) for items in groups.values())
print(f"wrote {OUT_PATH}: {total} endpoints in {len(groups)} packages")
if __name__ == "__main__":
main()