from apscheduler.executors.asyncio import AsyncIOExecutor from apscheduler.schedulers.asyncio import AsyncIOScheduler from contextlib import asynccontextmanager from datetime import datetime from fastapi import FastAPI, Request from fastapi.responses import FileResponse, PlainTextResponse, Response from pathlib import Path from typing import Any import httpx import json import subprocess LOGFILE = Path.home() / "a.json" class PrettyJSONResponse(Response): media_type = "application/json" def render(self, content: Any) -> bytes: return json.dumps(content, indent=2).encode("utf-8") @asynccontextmanager async def lifespan(app: FastAPI): scheduler.start() yield scheduler.shutdown() app = FastAPI(lifespan=lifespan) @app.middleware("http") async def log_request(request: Request, call_next: Any): ts = datetime.now().strftime("%y%m%d%H%M%S%f") data = { "day": int(ts[:6]), "dt": int(ts[:-3]), "url": request.url, "query_params": request.query_params, "client": request.client.host, "method": request.method, "headers": dict(request.headers), } output = json.dumps( obj=data, default=str, indent=None, separators=(", ", ":"), ) with open(LOGFILE, "a") as f: separator = "\n" if f.tell() else "" f.write(separator + output) response = await call_next(request) return response @app.get("/") def read_root(request: Request): """Main URL returning an executable installer script. Query parameters can be used to install specific things, e.g. curl -fsSL "https://pup-py-fetch.hf.space?python=3.11&pixi=marimo&myenv=cowsay,duckdb" Package specs "duckdb>=1.1" are not supported. """ query_params = dict(request.query_params) py_ver = query_params.pop("python", "3.12") if "Windows" in request.headers.get("user-agent"): pup_url = "https://raw.githubusercontent.com/liquidcarbon/puppy/main/pup.ps1" script = [ f"$pup_ps1 = (iwr -useb {pup_url}).Content", f"& ([scriptblock]::Create($pup_ps1)) {py_ver}", ] else: pup_url = "https://raw.githubusercontent.com/liquidcarbon/puppy/main/pup.sh" script = [f"curl -fsSL {pup_url} | bash -s {py_ver}"] pixi_packages = query_params.pop("pixi", "") if pixi_packages: for pkg in pixi_packages.split(","): script.append(f"pixi add {pkg}") # remaining query params are venvs for venv, uv_packages in query_params.items(): for pkg in uv_packages.split(","): script.append(f"pup add {venv} {pkg}") script.append("# script ends here\n") return PlainTextResponse("\n".join(script)) @app.get("/a", response_class=PrettyJSONResponse) def get_analytics(n: int = 5): if n == 0: cmd = f"cat {LOGFILE.as_posix()}" else: cmd = f"tail -{n} {LOGFILE.as_posix()}" json_lines = subprocess.run(cmd.split(), capture_output=True).stdout content = json.loads(f"[{json_lines.replace(b"\n", b",").decode()}]") return content @app.api_route("/qa", response_class=FileResponse, methods=["GET", "HEAD"]) def query_analytics(): return LOGFILE.as_posix() @app.get("/favicon.ico") async def favicon(): return {"message": "woof!"} @app.get("/ping") async def ping(): return {"message": "woof!"} async def self_ping(): async with httpx.AsyncClient() as client: _ = await client.get("http://0.0.0.0:7860/ping") scheduler = AsyncIOScheduler(executors={"default": AsyncIOExecutor()}) scheduler.add_job(self_ping, "interval", minutes=60) if __name__ == "__main__": import uvicorn fmt = "%(asctime)s %(levelprefix)s %(message)s" uvicorn_logging = uvicorn.config.LOGGING_CONFIG uvicorn_logging["formatters"]["access"]["datefmt"] = "%y%m%d @ %T" uvicorn_logging["formatters"]["access"]["fmt"] = fmt uvicorn_logging["formatters"]["default"]["datefmt"] = "%y%m%d @ %T" uvicorn_logging["formatters"]["default"]["fmt"] = fmt uvicorn.run(app, host="0.0.0.0", port=7860)