Spaces:
Sleeping
Sleeping
jayanth922
commited on
Commit
·
5332e69
1
Parent(s):
fcc2e99
chore: remove optional Modal job and VS Code tasks
Browse files- .vscode/tasks.json +0 -47
- modal/spec_refresh.py +0 -117
.vscode/tasks.json
DELETED
|
@@ -1,47 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"version": "2.0.0",
|
| 3 |
-
"tasks": [
|
| 4 |
-
{
|
| 5 |
-
"label": "Run (httpbin profile)",
|
| 6 |
-
"type": "shell",
|
| 7 |
-
"command": "${workspaceFolder}/.venv/bin/python",
|
| 8 |
-
"args": ["app.py", "--profile", "httpbin"],
|
| 9 |
-
"options": { "cwd": "${workspaceFolder}" },
|
| 10 |
-
"problemMatcher": []
|
| 11 |
-
},
|
| 12 |
-
{
|
| 13 |
-
"label": "Run (petstore3 profile)",
|
| 14 |
-
"type": "shell",
|
| 15 |
-
"command": "${workspaceFolder}/.venv/bin/python",
|
| 16 |
-
"args": ["app.py", "--profile", "petstore3"],
|
| 17 |
-
"options": { "cwd": "${workspaceFolder}" },
|
| 18 |
-
"problemMatcher": []
|
| 19 |
-
},
|
| 20 |
-
{
|
| 21 |
-
"label": "Run (custom flags)",
|
| 22 |
-
"type": "shell",
|
| 23 |
-
"command": "${workspaceFolder}/.venv/bin/python",
|
| 24 |
-
"args": [
|
| 25 |
-
"app.py",
|
| 26 |
-
"--spec","${input:spec}",
|
| 27 |
-
"--base-url","${input:base}",
|
| 28 |
-
"--bearer","${input:bearer}"
|
| 29 |
-
],
|
| 30 |
-
"options": { "cwd": "${workspaceFolder}" },
|
| 31 |
-
"problemMatcher": []
|
| 32 |
-
},
|
| 33 |
-
{
|
| 34 |
-
"label": "Run (hot reload via gradio CLI)",
|
| 35 |
-
"type": "shell",
|
| 36 |
-
"command": "${workspaceFolder}/.venv/bin/gradio",
|
| 37 |
-
"args": ["app.py"],
|
| 38 |
-
"options": { "cwd": "${workspaceFolder}" },
|
| 39 |
-
"problemMatcher": []
|
| 40 |
-
}
|
| 41 |
-
],
|
| 42 |
-
"inputs": [
|
| 43 |
-
{ "id":"spec", "type":"promptString", "description":"OpenAPI spec URL/path", "default":"https://petstore3.swagger.io/api/v3/openapi.json" },
|
| 44 |
-
{ "id":"base", "type":"promptString", "description":"Base URL", "default":"https://petstore3.swagger.io/api/v3" },
|
| 45 |
-
{ "id":"bearer", "type":"promptString", "description":"Bearer token (optional)", "default":"" }
|
| 46 |
-
]
|
| 47 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modal/spec_refresh.py
DELETED
|
@@ -1,117 +0,0 @@
|
|
| 1 |
-
# modal/spec_refresh.py
|
| 2 |
-
from __future__ import annotations
|
| 3 |
-
import os, json, time, yaml, requests
|
| 4 |
-
from pathlib import Path
|
| 5 |
-
import modal
|
| 6 |
-
|
| 7 |
-
PROFILES_REMOTE_PATH = "/workspace/profiles.yaml" # baked into the image
|
| 8 |
-
|
| 9 |
-
# Build container image with deps + bake profiles.yaml into it
|
| 10 |
-
image = (
|
| 11 |
-
modal.Image.debian_slim()
|
| 12 |
-
.pip_install(
|
| 13 |
-
"requests",
|
| 14 |
-
"pyyaml",
|
| 15 |
-
"openapi-spec-validator",
|
| 16 |
-
"fastapi[standard]" # required for @fastapi_endpoint
|
| 17 |
-
)
|
| 18 |
-
.add_local_file("profiles.yaml", remote_path=PROFILES_REMOTE_PATH) # ⬅️ no Mounts
|
| 19 |
-
)
|
| 20 |
-
|
| 21 |
-
app = modal.App(name="anyapi-mcp-spec-refresh", image=image)
|
| 22 |
-
|
| 23 |
-
# Durable cache for validated specs
|
| 24 |
-
VOLUME_NAME = "anyapi-oas-cache"
|
| 25 |
-
volume = modal.Volume.from_name(VOLUME_NAME, create_if_missing=True)
|
| 26 |
-
CACHE_DIR = Path("/cache")
|
| 27 |
-
|
| 28 |
-
# ---------- helpers ----------
|
| 29 |
-
def _load_profiles() -> dict:
|
| 30 |
-
if not os.path.exists(PROFILES_REMOTE_PATH):
|
| 31 |
-
# Keep graceful behavior if file missing
|
| 32 |
-
return {}
|
| 33 |
-
with open(PROFILES_REMOTE_PATH, "r", encoding="utf-8") as f:
|
| 34 |
-
return yaml.safe_load(f) or {}
|
| 35 |
-
|
| 36 |
-
def _validate_oas(spec_obj: dict) -> tuple[bool, str]:
|
| 37 |
-
from openapi_spec_validator import validate_spec
|
| 38 |
-
try:
|
| 39 |
-
validate_spec(spec_obj)
|
| 40 |
-
return True, "ok"
|
| 41 |
-
except Exception as e:
|
| 42 |
-
return False, f"{type(e).__name__}: {e}"
|
| 43 |
-
|
| 44 |
-
def _coerce_yaml_or_json(text: str) -> dict:
|
| 45 |
-
try:
|
| 46 |
-
return json.loads(text)
|
| 47 |
-
except json.JSONDecodeError:
|
| 48 |
-
return yaml.safe_load(text)
|
| 49 |
-
|
| 50 |
-
def _now_iso() -> str:
|
| 51 |
-
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
| 52 |
-
|
| 53 |
-
# ---------- job: refresh all ----------
|
| 54 |
-
@app.function(
|
| 55 |
-
image=image,
|
| 56 |
-
volumes={CACHE_DIR: volume},
|
| 57 |
-
schedule=modal.Cron("0 */6 * * *", timezone="America/Los_Angeles"),
|
| 58 |
-
retries=modal.Retries(max_retries=2),
|
| 59 |
-
)
|
| 60 |
-
def refresh_all():
|
| 61 |
-
profiles = _load_profiles()
|
| 62 |
-
results = {}
|
| 63 |
-
for name, cfg in (profiles or {}).items():
|
| 64 |
-
spec_url = (cfg or {}).get("spec")
|
| 65 |
-
base_url = (cfg or {}).get("base_url")
|
| 66 |
-
if not spec_url:
|
| 67 |
-
results[name] = {"ok": False, "error": "missing spec url"}
|
| 68 |
-
continue
|
| 69 |
-
try:
|
| 70 |
-
r = requests.get(spec_url, timeout=30)
|
| 71 |
-
r.raise_for_status()
|
| 72 |
-
spec_obj = _coerce_yaml_or_json(r.text)
|
| 73 |
-
ok, msg = _validate_oas(spec_obj)
|
| 74 |
-
except Exception as e:
|
| 75 |
-
ok, msg, spec_obj = False, f"{type(e).__name__}: {e}", None
|
| 76 |
-
|
| 77 |
-
prof_dir = CACHE_DIR / name
|
| 78 |
-
prof_dir.mkdir(parents=True, exist_ok=True)
|
| 79 |
-
meta = {
|
| 80 |
-
"profile": name,
|
| 81 |
-
"spec_url": spec_url,
|
| 82 |
-
"base_url": base_url,
|
| 83 |
-
"last_checked": _now_iso(),
|
| 84 |
-
"ok": ok,
|
| 85 |
-
"message": msg,
|
| 86 |
-
}
|
| 87 |
-
(prof_dir / "meta.json").write_text(json.dumps(meta, indent=2), encoding="utf-8")
|
| 88 |
-
if ok and isinstance(spec_obj, dict):
|
| 89 |
-
(prof_dir / "openapi.json").write_text(json.dumps(spec_obj, indent=2), encoding="utf-8")
|
| 90 |
-
results[name] = meta
|
| 91 |
-
|
| 92 |
-
(CACHE_DIR / "index.json").write_text(json.dumps(results, indent=2), encoding="utf-8")
|
| 93 |
-
volume.commit()
|
| 94 |
-
return results
|
| 95 |
-
|
| 96 |
-
# one-off local run
|
| 97 |
-
@app.local_entrypoint()
|
| 98 |
-
def run_once():
|
| 99 |
-
out = refresh_all.remote()
|
| 100 |
-
print(json.dumps(out, indent=2))
|
| 101 |
-
|
| 102 |
-
# ---------- tiny web endpoints ----------
|
| 103 |
-
@app.function(image=image, volumes={CACHE_DIR: volume})
|
| 104 |
-
@modal.fastapi_endpoint(docs=True)
|
| 105 |
-
def status():
|
| 106 |
-
idx = CACHE_DIR / "index.json"
|
| 107 |
-
if idx.exists():
|
| 108 |
-
return json.loads(idx.read_text(encoding="utf-8"))
|
| 109 |
-
return {"error": "no runs yet"}
|
| 110 |
-
|
| 111 |
-
@app.function(image=image, volumes={CACHE_DIR: volume})
|
| 112 |
-
@modal.fastapi_endpoint()
|
| 113 |
-
def download(profile: str):
|
| 114 |
-
p = CACHE_DIR / profile / "openapi.json"
|
| 115 |
-
if not p.exists():
|
| 116 |
-
return {"error": f"no spec for profile '{profile}' (run refresh or check status)"}
|
| 117 |
-
return json.loads(p.read_text(encoding="utf-8"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|