fetch / fetch.py
pup-py's picture
switch to uvicorn for custom logging
058dfe9
raw
history blame
4.11 kB
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)