171 lines
5.3 KiB
Python
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()
|