feat: Adobe Sign template download pipeline

download_templates.py — subcommand CLI for listing and downloading library
templates from Adobe Sign.

  list                   — print all templates with name, modified date, ID
  download               — download all templates (default)
  download --all         — explicit download all
  download "Name"        — download a single named template; picks the most
                           recently modified if duplicates exist

Each template is saved to downloads/<name>__<id8>/ containing metadata.json,
form_fields.json, documents.json, and the source PDF.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Paul Huliganga 2026-04-15 19:44:52 -04:00
parent 343955241d
commit e655d8b4f5
1 changed files with 162 additions and 0 deletions

162
src/download_templates.py Normal file
View File

@ -0,0 +1,162 @@
"""
Download Adobe Sign library templates to the local downloads/ folder.
Usage:
python3 src/download_templates.py list
List all templates available in the account (no download).
python3 src/download_templates.py download
python3 src/download_templates.py download --all
Download all templates.
python3 src/download_templates.py download "Template Name"
Download the named template. If multiple templates share the same name,
the most recently modified one is used.
"""
import argparse
import json
import os
import sys
from dotenv import load_dotenv
load_dotenv()
sys.path.insert(0, os.path.dirname(__file__))
from adobe_api import adobe_api_get, adobe_api_get_bytes
DOWNLOADS_DIR = os.path.join(os.path.dirname(__file__), "..", "downloads")
def safe_dirname(name):
return "".join(c if c.isalnum() or c in " -_" else "_" for c in name).strip()
def save_json(path, data):
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
def fetch_template_list():
result = adobe_api_get("libraryDocuments")
return result.get("libraryDocumentList", [])
def cmd_list(_args):
print("Fetching template list...")
templates = fetch_template_list()
if not templates:
print("No templates found.")
return
print(f"{'Name':<45} {'Modified':<25} {'ID'}")
print("-" * 100)
for t in sorted(templates, key=lambda x: x.get("modifiedDate", ""), reverse=True):
print(f"{t['name']:<45} {t.get('modifiedDate', 'n/a'):<25} {t['id']}")
def download_one(template):
template_id = template["id"]
template_name = template["name"]
dir_name = f"{safe_dirname(template_name)}__{template_id[:8]}"
out_dir = os.path.join(DOWNLOADS_DIR, dir_name)
os.makedirs(out_dir, exist_ok=True)
print(f"\n--- {template_name} ---")
metadata = adobe_api_get(f"libraryDocuments/{template_id}")
save_json(os.path.join(out_dir, "metadata.json"), metadata)
try:
form_fields = adobe_api_get(f"libraryDocuments/{template_id}/formFields")
save_json(os.path.join(out_dir, "form_fields.json"), form_fields)
print(f" {len(form_fields.get('fields', []))} form fields saved.")
except Exception as e:
print(f" WARNING: Could not fetch form fields: {e}")
save_json(os.path.join(out_dir, "form_fields.json"), {"error": str(e)})
docs = adobe_api_get(f"libraryDocuments/{template_id}/documents")
save_json(os.path.join(out_dir, "documents.json"), docs)
for doc in docs.get("documents", []):
doc_id = doc["id"]
doc_name = doc.get("name", doc_id)
if not doc_name.lower().endswith(".pdf"):
doc_name += ".pdf"
safe_name = safe_dirname(doc_name)
pdf_path = os.path.join(out_dir, safe_name)
try:
pdf_bytes = adobe_api_get_bytes(
f"libraryDocuments/{template_id}/documents/{doc_id}"
)
with open(pdf_path, "wb") as f:
f.write(pdf_bytes)
print(f" PDF saved ({len(pdf_bytes) // 1024}KB) → {safe_name}")
except Exception as e:
print(f" WARNING: Could not download PDF: {e}")
print(f" Done → downloads/{dir_name}/")
return out_dir
def cmd_download(args):
os.makedirs(DOWNLOADS_DIR, exist_ok=True)
templates = fetch_template_list()
if not templates:
print("No templates found in the account.")
return
# Specific template name requested
if args.template_name:
matches = [t for t in templates if t["name"] == args.template_name]
if not matches:
print(f"ERROR: No template named '{args.template_name}' found.")
print("Run 'list' to see available templates.")
sys.exit(1)
if len(matches) > 1:
print(f" {len(matches)} templates named '{args.template_name}' — using most recently modified.")
target = max(matches, key=lambda t: t.get("modifiedDate", ""))
download_one(target)
return
# --all or default (no name given)
print(f"Downloading all {len(templates)} template(s)...")
for t in templates:
download_one(t)
print(f"\nAll templates downloaded to: {os.path.abspath(DOWNLOADS_DIR)}")
def main():
parser = argparse.ArgumentParser(
description="Download Adobe Sign library templates",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
subparsers = parser.add_subparsers(dest="command")
subparsers.add_parser("list", help="List all templates in the account")
dl_parser = subparsers.add_parser("download", help="Download templates")
dl_parser.add_argument(
"template_name", nargs="?", default=None,
metavar="TEMPLATE_NAME",
help="Name of template to download. Omit (or use --all) to download all.",
)
dl_parser.add_argument(
"--all", dest="download_all", action="store_true",
help="Download all templates (default when no name is given)",
)
args = parser.parse_args()
if args.command == "list":
cmd_list(args)
elif args.command == "download":
cmd_download(args)
else:
parser.print_help()
if __name__ == "__main__":
main()