Spaces:
Running
Running
File size: 5,322 Bytes
be37871 938d45b d328dbf ca01fa3 b34d742 b8b73b2 121923b fdfb0b2 e0801c9 fdfb0b2 359ff3d cac41bd 121923b 0daac8b be37871 0daac8b be37871 0daac8b 21e70fe ca01fa3 a180fd2 e0801c9 ca01fa3 57ea732 ca01fa3 21e70fe 1270bff ca01fa3 bc2b550 05acf81 bc2b550 05acf81 57ea732 cac41bd 05acf81 cac41bd 5376af4 01575eb 05acf81 57ea732 ca01fa3 c9fc495 05acf81 7f31eb6 05acf81 57ea732 938d45b cac41bd 5376af4 938d45b 05acf81 cac41bd 5376af4 05acf81 bc2b550 01575eb b8b73b2 57ea732 d328dbf b8b73b2 57ea732 13ae1a3 b8b73b2 cac41bd 5376af4 57ea732 cac41bd 13ae1a3 57ea732 cac41bd 4df7999 5266fe9 57ea732 05acf81 cac41bd 5376af4 b4c0638 05acf81 b34d742 57ea732 938d45b cac41bd 5376af4 938d45b 9f88800 57ea732 0daac8b 9f88800 0daac8b 9f88800 fdfb0b2 893e71a cac41bd 5376af4 893e71a 825e272 fdfb0b2 be37871 fdfb0b2 |
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 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
"""The FastAPI server for serving the LynxKite application."""
import shutil
import pydantic
import fastapi
import importlib
import pathlib
import pkgutil
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.gzip import GZipMiddleware
import starlette
from lynxkite.core import ops
from lynxkite.core import workspace
from . import crdt
def detect_plugins():
plugins = {}
for _, name, _ in pkgutil.iter_modules():
if name.startswith("lynxkite_"):
print(f"Importing {name}")
plugins[name] = importlib.import_module(name)
if not plugins:
print("No LynxKite plugins found. Be sure to install some!")
return plugins
lynxkite_plugins = detect_plugins()
ops.save_catalogs("plugins loaded")
app = fastapi.FastAPI(lifespan=crdt.lifespan)
app.include_router(crdt.router)
app.add_middleware(GZipMiddleware)
@app.get("/api/catalog")
def get_catalog(workspace: str):
ops.load_user_scripts(workspace)
return {k: {op.name: op.model_dump() for op in v.values()} for k, v in ops.CATALOGS.items()}
class SaveRequest(workspace.BaseConfig):
path: str
ws: workspace.Workspace
data_path = pathlib.Path()
def save(req: SaveRequest):
path = data_path / req.path
assert path.is_relative_to(data_path), f"Path '{path}' is invalid"
req.ws.save(path)
@app.post("/api/save")
async def save_and_execute(req: SaveRequest):
save(req)
if req.ws.has_executor():
await req.ws.execute()
save(req)
return req.ws
@app.post("/api/delete")
async def delete_workspace(req: dict):
json_path: pathlib.Path = data_path / req["path"]
crdt_path: pathlib.Path = data_path / ".crdt" / f"{req['path']}.crdt"
assert json_path.is_relative_to(data_path), f"Path '{json_path}' is invalid"
json_path.unlink()
crdt_path.unlink()
@app.get("/api/load")
def load(path: str):
path = data_path / path
assert path.is_relative_to(data_path), f"Path '{path}' is invalid"
if not path.exists():
return workspace.Workspace()
return workspace.Workspace.load(path)
class DirectoryEntry(pydantic.BaseModel):
name: str
type: str
def _get_path_type(path: pathlib.Path) -> str:
if path.is_dir():
return "directory"
elif path.suffixes[-2:] == [".lynxkite", ".json"]:
return "workspace"
else:
return "file"
@app.get("/api/dir/list")
def list_dir(path: str):
path = data_path / path
assert path.is_relative_to(data_path), f"Path '{path}' is invalid"
return sorted(
[
DirectoryEntry(
name=str(p.relative_to(data_path)),
type=_get_path_type(p),
)
for p in path.iterdir()
if not p.name.startswith(".")
],
key=lambda x: (x.type != "directory", x.name.lower()),
)
@app.post("/api/dir/mkdir")
def make_dir(req: dict):
path = data_path / req["path"]
assert path.is_relative_to(data_path), f"Path '{path}' is invalid"
assert not path.exists(), f"{path} already exists"
path.mkdir()
@app.post("/api/dir/delete")
def delete_dir(req: dict):
path: pathlib.Path = data_path / req["path"]
assert all([path.is_relative_to(data_path), path.exists(), path.is_dir()]), (
f"Path '{path}' is invalid"
)
shutil.rmtree(path)
@app.get("/api/service/{module_path:path}")
async def service_get(req: fastapi.Request, module_path: str):
"""Executors can provide extra HTTP APIs through the /api/service endpoint."""
module = lynxkite_plugins[module_path.split("/")[0]]
return await module.api_service_get(req)
@app.post("/api/service/{module_path:path}")
async def service_post(req: fastapi.Request, module_path: str):
"""Executors can provide extra HTTP APIs through the /api/service endpoint."""
module = lynxkite_plugins[module_path.split("/")[0]]
return await module.api_service_post(req)
@app.post("/api/upload")
async def upload(req: fastapi.Request):
"""Receives file uploads and stores them in DATA_PATH."""
form = await req.form()
for file in form.values():
file_path = data_path / "uploads" / file.filename
assert file_path.is_relative_to(data_path), f"Path '{file_path}' is invalid"
with file_path.open("wb") as buffer:
shutil.copyfileobj(file.file, buffer)
return {"status": "ok"}
@app.post("/api/execute_workspace")
async def execute_workspace(name: str):
"""Trigger and await the execution of a workspace."""
room = await crdt.ws_websocket_server.get_room(name)
ws_pyd = workspace.Workspace.model_validate(room.ws.to_py())
await crdt.execute(name, room.ws, ws_pyd)
class SPAStaticFiles(StaticFiles):
"""Route everything to index.html. https://stackoverflow.com/a/73552966/3318517"""
async def get_response(self, path: str, scope):
try:
return await super().get_response(path, scope)
except (
fastapi.HTTPException,
starlette.exceptions.HTTPException,
) as ex:
if ex.status_code == 404:
return await super().get_response(".", scope)
else:
raise ex
static_dir = SPAStaticFiles(packages=[("lynxkite_app", "web_assets")], html=True)
app.mount("/", static_dir, name="web_assets")
|