from __future__ import annotations
import os
from pathlib import Path
import paramiko
def _sftp_client() -> tuple[paramiko.SFTPClient, paramiko.Transport]:
host = (os.environ.get("SFTP_HOST") or "").strip()
user = (os.environ.get("SFTP_USER") or "").strip()
port = int((os.environ.get("SFTP_PORT") or "22").strip() or "22")
password = os.environ.get("SFTP_PASS")
if not host or not user:
raise RuntimeError("Missing SFTP_HOST or SFTP_USER")
if not password:
raise RuntimeError("Missing SFTP_PASS")
t = paramiko.Transport((host, port))
# force password-only auth
t.connect(username=user, password=password)
return paramiko.SFTPClient.from_transport(t), t
def _mkdir_p(sftp: paramiko.SFTPClient, remote_dir: str) -> None:
parts = [p for p in remote_dir.strip("/").split("/") if p]
cur = ""
for part in parts:
cur += "/" + part
try:
sftp.stat(cur)
except FileNotFoundError:
sftp.mkdir(cur)
def store_to_sftp(
pdf_id: str,
template_id: str,
cfg_json_bytes: bytes,
pdf_bytes: bytes,
pdf_name: str,
) -> str:
"""
Remote layout:
///
trainer_config___.json
.pdf>
"""
base = (os.environ.get("SFTP_BASE_DIR") or "/").strip()
if not base.startswith("/"):
base = "/" + base
base = base.rstrip("/") or "/"
sftp, transport = _sftp_client()
try:
remote_dir = f"{base}/{template_id}/{pdf_id}".replace("//", "/")
_mkdir_p(sftp, remote_dir)
remote_cfg = f"{remote_dir}/trainer_config_{pdf_id}__{template_id}.json"
remote_pdf_name = (pdf_name or f"{pdf_id}.pdf").lstrip("/")
remote_pdf = f"{remote_dir}/{remote_pdf_name}"
with sftp.open(remote_cfg, "wb") as f:
f.write(cfg_json_bytes)
with sftp.open(remote_pdf, "wb") as f:
f.write(pdf_bytes)
return remote_dir
finally:
try:
sftp.close()
finally:
transport.close()