Spaces:
Running
Running
Download scripts/check_env.py from Shurem/mz_chatbot: direct link, hf CLI and curl.
- Browser
- Download file 5.14 kB
-
https://huggingface.co/spaces/Shurem/mz_chatbot/resolve/main/scripts/check_env.py
- Command line
-
hf download hf://spaces/Shurem/mz_chatbot/scripts/check_env.py
-
curl -L -o check_env.py https://huggingface.co/spaces/Shurem/mz_chatbot/resolve/main/scripts/check_env.py
5.14 kB
| """ | |
| check_env.py β resolve and report the database configuration WITHOUT connecting. | |
| Answers "is my .env actually valid?" before you run alembic. Stdlib only, so it | |
| works before `uv sync`. Passwords are never printed. | |
| python3 scripts/check_env.py | |
| Common trap this catches: DATABASE_URL is a URL, so a password containing | |
| @ : / ? # % must be percent-encoded β Neon's own connection string is already | |
| encoded correctly, so this usually only bites if the password was edited by hand. | |
| """ | |
| from __future__ import annotations | |
| import sys | |
| from pathlib import Path | |
| from urllib.parse import unquote, urlsplit | |
| BACKEND = Path(__file__).resolve().parent.parent | |
| REPO_ROOT = BACKEND.parent | |
| NEEDS_ENCODING = set("@:/?#[]% ") | |
| def load_env_file(path: Path) -> dict[str, str]: | |
| """Minimal .env reader β same precedence rule as config.py (first wins).""" | |
| values: dict[str, str] = {} | |
| if not path.exists(): | |
| return values | |
| for raw in path.read_text(encoding="utf-8").splitlines(): | |
| line = raw.strip() | |
| if not line or line.startswith("#") or "=" not in line: | |
| continue | |
| key, _, val = line.partition("=") | |
| val = val.strip() | |
| if len(val) >= 2 and val[0] == val[-1] and val[0] in "\"'": | |
| val = val[1:-1] | |
| values.setdefault(key.strip(), val) | |
| return values | |
| def main() -> int: | |
| env = load_env_file(BACKEND / ".env") | |
| for k, v in load_env_file(REPO_ROOT / ".env").items(): | |
| env.setdefault(k, v) | |
| if not (BACKEND / ".env").exists(): | |
| print(f"ERROR: {BACKEND / '.env'} does not exist. Copy .env.example to .env.") | |
| return 1 | |
| problems: list[str] = [] | |
| url = env.get("DATABASE_URL", "").strip() | |
| print(f"backend .env : {BACKEND / '.env'}") | |
| print(f"root .env : {REPO_ROOT / '.env'}" | |
| f"{'' if (REPO_ROOT / '.env').exists() else ' (absent)'}") | |
| print() | |
| if not url: | |
| problems.append("DATABASE_URL is empty β paste the Neon connection string in") | |
| else: | |
| parts = urlsplit(url) | |
| driver, host, port = parts.scheme, parts.hostname, parts.port | |
| user = unquote(parts.username or "") | |
| raw_pw = parts.password or "" | |
| db = parts.path.lstrip("/") | |
| query = parts.query | |
| print(f"driver : {driver}") | |
| print(f"host : {host}") | |
| print(f"port : {port or 5432}") | |
| print(f"user : {user}") | |
| print(f"password : {'set (' + str(len(raw_pw)) + ' chars as written)' if raw_pw else 'MISSING'}") | |
| print(f"database : {db}") | |
| print(f"query : {query or '(none)'}") | |
| if driver != "postgresql+psycopg": | |
| problems.append( | |
| f"driver is {driver!r}; this backend expects postgresql+psycopg (psycopg v3)" | |
| ) | |
| if not host: | |
| problems.append("no host in DATABASE_URL") | |
| if host and "neon.tech" not in host: | |
| print(" (host does not look like a Neon endpoint β fine if intentional)") | |
| if not user: | |
| problems.append("no user in DATABASE_URL") | |
| if not raw_pw: | |
| problems.append("no password in DATABASE_URL") | |
| if not db: | |
| problems.append("no database name in DATABASE_URL (the path after the host)") | |
| if "sslmode=require" not in query and "sslmode=verify-full" not in query: | |
| problems.append("sslmode=require missing β Neon refuses plain-text connections") | |
| # A raw special character in the password truncates the URL silently. | |
| decoded = unquote(raw_pw) | |
| if decoded != raw_pw and any(c in NEEDS_ENCODING for c in decoded): | |
| print(" (password contains percent-encoded characters β good)") | |
| elif any(c in NEEDS_ENCODING for c in raw_pw): | |
| problems.append( | |
| "password contains a character that must be percent-encoded " | |
| f"({''.join(sorted(set(c for c in raw_pw if c in NEEDS_ENCODING)))}) β " | |
| "urlsplit has probably mis-parsed this URL" | |
| ) | |
| print() | |
| print("service settings") | |
| for key, default in ( | |
| ("PUBLIC_BASE_URL", "http://127.0.0.1:8000"), | |
| ("CORS_ORIGINS", "http://localhost:3000,http://127.0.0.1:3000"), | |
| ("SCREENSHOT_DIR", "screenshots"), | |
| ): | |
| val = env.get(key, "").strip() | |
| print(f" {key:16s}: {val or default}{' (default)' if not val else ''}") | |
| print(f" {'API_KEY':16s}: {'set β X-API-Key required' if env.get('API_KEY','').strip() else 'empty β auth disabled (fine for localhost)'}") | |
| print() | |
| print("third-party keys (presence only)") | |
| for key in ("GEMINI_API_KEY", "HUNTER_API_KEY", "SIGNALHIRE_API_KEY", "APOLLO_API_KEY", "CALENDAR_LINK"): | |
| print(f" {key:20s}: {'set' if env.get(key, '').strip() else 'not set'}") | |
| print() | |
| if problems: | |
| print(f"{len(problems)} problem(s):") | |
| for p in problems: | |
| print(f" - {p}") | |
| return len(problems) | |
| print("config looks valid. Next: uv run alembic upgrade head") | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |