adobe-to-docusign-migrator/src/migrate_template.py

173 lines
5.5 KiB
Python

"""
migrate_template.py
-------------------
End-to-end migration: downloads an Adobe Sign template, converts it to
DocuSign format, and uploads it to DocuSign.
Usage:
python3 src/migrate_template.py --list
Show all Adobe Sign templates available to download.
python3 src/migrate_template.py --template "Template Name"
Download, convert, and upload the named template.
If multiple templates share the same name, the most recently modified
one is used.
python3 src/migrate_template.py --template "Template Name" --skip-upload
Download and convert only — writes the DocuSign JSON to
migration-output/ without uploading.
"""
import argparse
import os
import sys
from pathlib import Path
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
from compose_docusign_template import compose_template
from upload_docusign_template import upload_template
DOWNLOADS_DIR = Path(__file__).parent.parent / "downloads"
OUTPUT_DIR = Path(__file__).parent.parent / "migration-output"
def safe_dirname(name):
return "".join(c if c.isalnum() or c in " -_" else "_" for c in name).strip()
def save_json(path, data):
import json
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():
print("Fetching template list from Adobe Sign...")
templates = fetch_template_list()
if not templates:
print("No templates found.")
return
print(f"\n{'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 find_template(template_name, templates):
matches = [t for t in templates if t["name"] == template_name]
if not matches:
print(f"ERROR: No template named '{template_name}' found in Adobe Sign.")
print("Run --list to see available templates.")
sys.exit(1)
if len(matches) > 1:
print(f" {len(matches)} templates named '{template_name}' — using most recently modified.")
return max(matches, key=lambda t: t.get("modifiedDate", ""))
def download_template(template) -> Path:
import json
template_id = template["id"]
template_name = template["name"]
dir_name = f"{safe_dirname(template_name)}__{template_id[:8]}"
out_dir = DOWNLOADS_DIR / dir_name
out_dir.mkdir(parents=True, exist_ok=True)
print(f"\nDownloading '{template_name}' → downloads/{dir_name}/")
metadata = adobe_api_get(f"libraryDocuments/{template_id}")
save_json(out_dir / "metadata.json", metadata)
try:
form_fields = adobe_api_get(f"libraryDocuments/{template_id}/formFields")
save_json(out_dir / "form_fields.json", form_fields)
field_count = len(form_fields.get("fields", []))
print(f" {field_count} form fields")
except Exception as e:
print(f" WARNING: Could not fetch form fields: {e}")
save_json(out_dir / "form_fields.json", {"error": str(e)})
docs = adobe_api_get(f"libraryDocuments/{template_id}/documents")
save_json(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)
try:
pdf_bytes = adobe_api_get_bytes(
f"libraryDocuments/{template_id}/documents/{doc_id}"
)
with open(out_dir / safe_name, "wb") as f:
f.write(pdf_bytes)
print(f" PDF ({len(pdf_bytes) // 1024}KB) → {safe_name}")
except Exception as e:
print(f" WARNING: Could not download PDF: {e}")
return out_dir
def convert_template(template_dir: Path) -> Path:
output_path = OUTPUT_DIR / template_dir.name / "docusign-template.json"
print(f"\nConverting to DocuSign format...")
_, warnings, _ = compose_template(str(template_dir), str(output_path))
print(f" Written: {output_path}")
for w in warnings:
print(f" WARNING: {w}")
return output_path
def main():
parser = argparse.ArgumentParser(
description="Migrate an Adobe Sign template to DocuSign",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
"--template", metavar="NAME",
help="Name of the Adobe Sign template to migrate"
)
group.add_argument(
"--list", action="store_true",
help="List available Adobe Sign templates"
)
parser.add_argument(
"--skip-upload", action="store_true",
help="Convert only — do not upload to DocuSign"
)
args = parser.parse_args()
DOWNLOADS_DIR.mkdir(exist_ok=True)
OUTPUT_DIR.mkdir(exist_ok=True)
if args.list:
cmd_list()
return
templates = fetch_template_list()
template = find_template(args.template, templates)
template_dir = download_template(template)
output_path = convert_template(template_dir)
if args.skip_upload:
print(f"\nSkipped upload. DocuSign JSON: {output_path}")
else:
print()
upload_template(str(output_path))
if __name__ == "__main__":
main()