"""Write the server's OpenAPI document, strict and normalized, to
ui/src/lib/generated/openapi.json - what `npm run gen:api` turns into the UI's
response types. `--check` exits 1 when the committed copy is stale;
`--stdout` prints instead of writing.
Normalized so it is the same on every machine and release: `info.version`
is "0", and the app is built with fixed directories and no token. Written
only under the FastAPI and Pydantic that constraints-openapi.txt pins, since
either one's release changes the document by itself."""
import json
import os
from importlib.metadata import version
import sys
import tempfile
from pathlib import Path
os.environ["DW_STRICT_RESPONSES"] = "1" # before dw is imported: no index signatures
REPO = Path(__file__).resolve().parent.parent
# This checkout's dw, not whichever one the venv's editable install points
# at - a worktree sharing the main checkout's venv would otherwise dump the
# main checkout's contract
sys.path.insert(0, str(REPO))
OPENAPI_PATH = REPO / "ui" / "src" / "lib" / "generated" / "openapi.json"
CONSTRAINTS = REPO / "constraints-openapi.txt"
def pins() -> dict[str, str]:
lines = (
line.split("#")[0].strip() for line in CONSTRAINTS.read_text().splitlines()
)
return dict(line.split("==") for line in lines if line)
def installed() -> dict[str, str]:
return {name: version(name) for name in pins()}
def unpinned() -> list[str]:
"""Each pinned package whose installed version differs, as "name x (pinned y)"."""
want, have = pins(), installed()
return [
f"{name} {have[name]} (pinned {want[name]})"
for name in want
if have[name] != want[name]
]
def openapi_document() -> dict:
with tempfile.TemporaryDirectory() as tmp:
# dw creates its settings root on import, and the app's JobManager
# opens the job history under it; point that at the scratch
# directory first, so reading the schema never touches the history
# a running dw.serve owns
os.environ["DIFFUSERS_HELPER_ROOT"] = os.path.join(tmp, "helper")
from dw.server.app import create_app
app = create_app(
workflow_dir=os.path.join(tmp, "workflows"),
output_dir=os.path.join(tmp, "outputs"),
prompt_dir=os.path.join(tmp, "prompts"),
)
try:
document = app.openapi()
finally:
app.state.job_manager.shutdown()
document["info"]["version"] = "0"
return document
def render() -> str:
return json.dumps(openapi_document(), indent=2, sort_keys=True) + "\n"
def main(argv: list[str]) -> int:
if "--stdout" in argv:
sys.stdout.write(render())
return 0
stray = unpinned()
if stray:
print(
f"not on the versions the document is pinned to: {', '.join(stray)} - "
f"pip install -c {CONSTRAINTS.name} -r requirements.txt"
)
return 2
text = render()
if "--check" in argv:
current = OPENAPI_PATH.read_text() if OPENAPI_PATH.exists() else ""
if current != text:
print(
f"{OPENAPI_PATH.relative_to(REPO)} is stale: run python scripts/dump_openapi.py"
)
return 1
return 0
OPENAPI_PATH.parent.mkdir(parents=True, exist_ok=True)
OPENAPI_PATH.write_text(text)
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))