feat: Click CLI wrapper for cantaloupe export
- cantaloupe/ package with click CLI (python -m cantaloupe export) - export command: --output, --headless/--visible, --scheduled, --email, --password - configure command: saves credentials to ~/.cantaloupe.env (600 perms) - cantaloupe/config.py: credential loading from env vars + env file - Updated src/auth.py _get_credentials() to also check ~/.cantaloupe.env - File size, row count (openpyxl), and elapsed time reporting - 39 new CLI/config tests + 4 updated auth tests; 86/86 pass
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
Credential management for cantaloupe CLI.
|
||||
|
||||
Reads credentials from:
|
||||
1. CANTALOUPE_EMAIL / CANTALOUPE_PASSWORD environment variables
|
||||
2. ~/.cantaloupe.env file (key=value format)
|
||||
|
||||
Saves credentials to ~/.cantaloupe.env with restrictive permissions (600).
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
ENV_FILE = Path.home() / ".cantaloupe.env"
|
||||
ENV_EMAIL_KEY = "CANTALOUPE_EMAIL"
|
||||
ENV_PASSWORD_KEY = "CANTALOUPE_PASSWORD"
|
||||
|
||||
|
||||
def load_credentials() -> tuple[str, str]:
|
||||
"""Load credentials from env vars or ~/.cantaloupe.env.
|
||||
|
||||
Priority: environment variables override the env file.
|
||||
Returns (email, password) — either or both may be empty strings.
|
||||
"""
|
||||
email = os.environ.get(ENV_EMAIL_KEY, "")
|
||||
password = os.environ.get(ENV_PASSWORD_KEY, "")
|
||||
|
||||
# If both are set via env, use them
|
||||
if email and password:
|
||||
return email, password
|
||||
|
||||
# Check the env file
|
||||
if ENV_FILE.exists():
|
||||
for line in ENV_FILE.read_text().splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
key, _, value = line.partition("=")
|
||||
key = key.strip()
|
||||
value = value.strip().strip('"').strip("'")
|
||||
if key == ENV_EMAIL_KEY and not email:
|
||||
email = value
|
||||
elif key == ENV_PASSWORD_KEY and not password:
|
||||
password = value
|
||||
|
||||
return email, password
|
||||
|
||||
|
||||
def save_credentials(email: str, password: str) -> Path:
|
||||
"""Save credentials to ~/.cantaloupe.env.
|
||||
|
||||
Updates the email/password lines in-place, preserving any other
|
||||
configuration lines that may be present. Sets file permissions to 600.
|
||||
|
||||
Returns the path to the env file.
|
||||
"""
|
||||
ENV_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Read existing lines, filtering out old credential entries
|
||||
lines: list[str] = []
|
||||
if ENV_FILE.exists():
|
||||
for line in ENV_FILE.read_text().splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped.startswith(f"{ENV_EMAIL_KEY}=") or stripped.startswith(
|
||||
f"{ENV_PASSWORD_KEY}="
|
||||
):
|
||||
continue
|
||||
lines.append(line)
|
||||
|
||||
# Append new credentials
|
||||
lines.append(f"{ENV_EMAIL_KEY}={email}")
|
||||
lines.append(f"{ENV_PASSWORD_KEY}={password}")
|
||||
|
||||
ENV_FILE.write_text("\n".join(lines) + "\n")
|
||||
ENV_FILE.chmod(0o600)
|
||||
|
||||
return ENV_FILE
|
||||
|
||||
|
||||
def prompt_credentials() -> tuple[str, str]:
|
||||
"""Prompt interactively for credentials.
|
||||
|
||||
Uses input() for email and getpass() for password.
|
||||
Returns (email, password) — empty strings if cancelled.
|
||||
"""
|
||||
try:
|
||||
import getpass
|
||||
|
||||
email = input("Cantaloupe email: ").strip()
|
||||
password = getpass.getpass("Cantaloupe password: ")
|
||||
return email, password
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
return "", ""
|
||||
Reference in New Issue
Block a user