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

171 lines
5.3 KiB
Python

"""
upload_docusign_template.py
---------------------------
Uploads a DocuSign template JSON file to DocuSign via the REST API.
Authenticates using DocuSign OAuth tokens stored in .env.
By default uses upsert: if a template with the same name already exists,
the most recently modified one is updated (PUT). Use --force-create to
always create a new template instead.
Usage:
python3 src/upload_docusign_template.py --file migration-output/<name>/docusign-template.json
python3 src/upload_docusign_template.py --file <path> --force-create
First-time setup:
python3 src/docusign_auth.py --authorize # authorize once
python3 src/upload_docusign_template.py --file <path>
Required .env keys (see docusign_auth.py for full list):
DOCUSIGN_CLIENT_ID, DOCUSIGN_CLIENT_SECRET, DOCUSIGN_ACCOUNT_ID,
DOCUSIGN_AUTH_SERVER, DOCUSIGN_BASE_URL, DOCUSIGN_REDIRECT_URI
"""
import argparse
import json
import os
import sys
from typing import Optional
import requests
from dotenv import load_dotenv
load_dotenv()
sys.path.insert(0, os.path.dirname(__file__))
from docusign_auth import get_access_token
def _make_headers(token: str) -> dict:
return {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
}
def _refresh_token_once(headers: dict) -> dict:
"""Clear cached token and return new headers with a fresh token."""
os.environ.pop("DOCUSIGN_ACCESS_TOKEN", None)
os.environ.pop("DOCUSIGN_TOKEN_EXPIRY", None)
return _make_headers(get_access_token())
def find_existing_template(
name: str,
account_id: str,
base_url: str,
headers: dict,
) -> Optional[str]:
"""
Search DocuSign for templates matching `name` exactly.
Returns the templateId of the most recently modified match, or None.
"""
url = f"{base_url}/v2.1/accounts/{account_id}/templates"
resp = requests.get(url, headers=headers, params={"search_text": name, "count": 100})
if resp.status_code == 401:
headers.update(_refresh_token_once(headers))
resp = requests.get(url, headers=headers, params={"search_text": name, "count": 100})
if not resp.ok:
return None
data = resp.json()
templates = data.get("envelopeTemplates") or data.get("templates") or []
# Exact name match only — search_text is a substring filter on DocuSign's side
exact = [t for t in templates if t.get("name") == name]
if not exact:
return None
# Most recently modified first
exact.sort(key=lambda t: t.get("lastModified", ""), reverse=True)
return exact[0]["templateId"]
def upload_template(file_path: str, force_create: bool = False) -> str:
"""
Upsert a template JSON file to DocuSign.
- If a template with the same name exists and force_create is False,
the most recently modified one is updated (PUT).
- Otherwise a new template is created (POST).
Returns the templateId.
"""
if not os.path.exists(file_path):
print(f"ERROR: File not found: {file_path}")
sys.exit(1)
with open(file_path) as f:
template = json.load(f)
account_id = os.getenv("DOCUSIGN_ACCOUNT_ID")
base_url = os.getenv("DOCUSIGN_BASE_URL", "https://demo.docusign.net/restapi")
if not account_id:
print("ERROR: DOCUSIGN_ACCOUNT_ID must be set in .env")
sys.exit(1)
headers = _make_headers(get_access_token())
template_name = template.get("name", file_path)
print(f"Uploading '{template_name}' to DocuSign...")
existing_id: Optional[str] = None
if not force_create:
existing_id = find_existing_template(template_name, account_id, base_url, headers)
if existing_id:
# Update existing template
url = f"{base_url}/v2.1/accounts/{account_id}/templates/{existing_id}"
resp = requests.put(url, headers=headers, json=template)
if resp.status_code == 401:
headers = _refresh_token_once(headers)
resp = requests.put(url, headers=headers, json=template)
if not resp.ok:
print(f"ERROR: Update failed ({resp.status_code})")
print(resp.text)
sys.exit(1)
print(f"Template updated: {existing_id}")
return existing_id
else:
# Create new template
url = f"{base_url}/v2.1/accounts/{account_id}/templates"
resp = requests.post(url, headers=headers, json=template)
if resp.status_code == 401:
headers = _refresh_token_once(headers)
resp = requests.post(url, headers=headers, json=template)
if not resp.ok:
print(f"ERROR: Upload failed ({resp.status_code})")
print(resp.text)
sys.exit(1)
result = resp.json()
template_id = result.get("templateId")
print(f"Template created: {template_id}")
return template_id
def main():
parser = argparse.ArgumentParser(
description="Upload a DocuSign template JSON to your DocuSign account"
)
parser.add_argument(
"--file", required=True,
help="Path to the docusign-template.json file to upload"
)
parser.add_argument(
"--force-create", action="store_true",
help="Always create a new template instead of updating an existing one"
)
args = parser.parse_args()
upload_template(args.file, force_create=args.force_create)
if __name__ == "__main__":
main()