File size: 5,842 Bytes
8c290e6 a06e733 bceb9b7 a0b1478 4731ef4 058dfe9 bceb9b7 8c290e6 bceb9b7 058dfe9 a06e733 058dfe9 bceb9b7 8c290e6 bceb9b7 a0b1478 26bf52a 96de55d 26bf52a c2219a8 a0b1478 96de55d a0b1478 c2219a8 a06e733 26bf52a 04d1a17 c2219a8 a06e733 c2219a8 04d1a17 c2219a8 a0b1478 c2219a8 96de55d c2219a8 96de55d a0b1478 c2219a8 04d1a17 c2219a8 bceb9b7 7de90bf 14f478a 7de90bf 14f478a 7de90bf bceb9b7 364dead bceb9b7 6d90cda 1594f3a 14f478a 6d90cda 14f478a bceb9b7 058dfe9 8c290e6 4731ef4 8c290e6 2c8258c a06e733 2c8258c a06e733 a0b1478 8c290e6 a06e733 d15e74f 058dfe9 |
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 184 185 186 |
from apscheduler.executors.asyncio import AsyncIOExecutor
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from contextlib import asynccontextmanager
from datetime import datetime, timedelta
from fastapi import FastAPI, Request
from fastapi.responses import (
FileResponse,
PlainTextResponse,
Response,
)
from os import environ, getenv
from pathlib import Path
from typing import Any
import httpx
import json
import subprocess
LOGFILE = Path.home() / "a.json"
PUP_URLS = {
"Linux": "https://raw.githubusercontent.com/liquidcarbon/puppy/main/pup.sh",
"Windows": "https://raw.githubusercontent.com/liquidcarbon/puppy/main/pup.ps1",
}
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.get("/")
def read_root(request: Request) -> Response:
"""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"
A slash ("/") in package name is interpreted as a GitHub repo.
Package specs "duckdb>=1.1" are not supported.
"""
query_params = dict(request.query_params)
# exclude internal endpoints
_ = query_params.pop("logs", "")
# python version
py_ver = query_params.pop("python", "3.12")
if "Windows" in request.headers.get("user-agent"):
pup_url = PUP_URLS.get("Windows")
script = [
f"$pup_ps1 = (iwr -useb {pup_url}).Content",
f"& ([scriptblock]::Create($pup_ps1)) {py_ver}",
]
hint = f"""iex (iwr "{request.url}").Content"""
else:
pup_url = PUP_URLS.get("Linux")
script = [f"curl -fsSL {pup_url} | bash -s {py_ver}"]
hint = f"""curl -fsSL "{request.url}" | bash"""
# pixi packages
pixi_packages = query_params.pop("pixi", "")
if pixi_packages:
for pkg in pixi_packages.split(","):
script.append(f"pixi add {pkg}")
# repos to be cloned
to_clone = query_params.pop("clone", "")
if to_clone:
for repo in to_clone.split(","):
if "/" in repo:
pkg = f"https://github.com/{repo}.git"
script.append(f"pup clone {pkg}")
else:
script.append(f"# can't clone `{repo}`: expected <username>/<reponame>")
# remaining query params are venvs
for venv, uv_packages in query_params.items():
for pkg in uv_packages.split(","):
if "/" in pkg: # slash implies GitHub repo
pkg = f"https://github.com/{pkg}.git"
script.append(f"pup add {venv} {pkg}")
script.extend(
[
"# 🐶 scripts end here; if you like what you see, copy-paste this recipe, or run like so:",
f"# {hint}",
"# to learn more, visit https://github.com/liquidcarbon/puppy\n",
]
)
return PlainTextResponse("\n".join(script))
@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,
"user-agent": request.headers.get("user-agent"),
"client": request.headers.get("x-forwarded-for"),
"private_ip": request.client.host,
"method": request.method,
"headers": str(request.headers),
}
output = json.dumps(obj=data, default=str, indent=None, separators=(", ", ":"))
with open(LOGFILE, "a") as f:
f.write(output + "\n")
response = await call_next(request)
return response
@app.get("/a", response_class=PrettyJSONResponse)
def get_analytics(n: int = 5):
if n == 0:
cmd = f"tac {LOGFILE.as_posix()}"
else:
cmd = f"tail -n {n} {LOGFILE.as_posix()} | tac"
_subprocess = subprocess.run(cmd, shell=True, text=True, capture_output=True)
json_lines, stderr = _subprocess.stdout[:-1], _subprocess.stderr
try:
content = json.loads(f"[ {json_lines.replace("\n", ",")} ]")
return content
except Exception as e:
return {"error": str(e), "stderr": stderr, "json_lines": json_lines}
@app.api_route("/qa", response_class=FileResponse, methods=["GET", "HEAD"])
def query_analytics():
return LOGFILE.as_posix()
@app.api_route("/env", response_class=PrettyJSONResponse)
def show_env():
return environ
@app.get("/favicon.ico")
async def favicon():
return {"message": "woof!"}
@app.get("/ping")
async def ping():
return {"message": "woof!"}
def self_ping():
self_host1 = getenv("SPACE_HOST", "0.0.0.0:7860")
self_host2 = "https://huggingface.co/spaces/pup-py/fetch"
with httpx.Client() as client:
_ = client.get(f"http://{self_host1}/ping", follow_redirects=True)
_ = client.get(self_host2, follow_redirects=True)
scheduler = AsyncIOScheduler(executors={"default": AsyncIOExecutor()})
scheduler.add_job(self_ping, next_run_time=datetime.now() + timedelta(seconds=30))
scheduler.add_job(self_ping, "interval", minutes=720)
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)
|