How Can We Help?
Bulk Upload of Profile photos using Python with Pure APIBulk Upload of Profile photos using Python with Pure API
Introduction
Pure currently only supports uploading one file at a time with a specific mime type. However, tools can be utilized to overcome this limitation and automate the process of uploading multiple files in bulk. The objective of this script is to enable users to bulk upload photos for specific persons in Pure Admin by leveraging Python to interact with the 'Person' and 'File-uploads' endpoints. The primary goal is to automate the process of updating person profiles with new photos based on UUIDs provided in a CSV file.
Pure API examples disclaimer
Disclaimer
• Proceed at your own risk. Running this script/function means you will not blame the author if this breaks your data.
• This script/function is provided AS IS without warranty of any kind. It is recommended to run them in a test-staging environment to assess the outcome and understand their functionality.
• Elsevier will not support or review any code that uses or is based on these examples. Nor will Elsevier support it in any other way.
• This script has been tested with Pure 5.31 but might not work for future versions of Pure.
• This script is developed for Python version 3.11 and have not been tested with any other version of Python.
• Please make sure any Pure server you run this script against, has been backed up prior to running this script, to insure you can return to a working Pure if things fail
Expertise level
Knowledge of Python is required. See also Python API Requests: Fundamental Coding Examples
Requirements
- Pure Admin Account
- Valid API Key
- API Documentation
- Python.
Setup Before Running the Script
Ensure the following prerequisites are met:
- Python 3.x installed.
- The necessary libraries must be installed:
requests
json
csv
- The CSV file (
csv_files.csv
) containing person UUIDs and photo file names must be located in the same directory as the script. - The Images folder (
images
) must contain the corresponding image files that match the names listed in the CSV file.
Sample CSV File Structure
The csv_files.csv
file should contain two columns:
- uuid: The UUID of the person whose profile will be updated.
-
photo_file: The name of the image file to be uploaded.
- Verify that the images folder contains the exact file names (case-sensitive).
- Verify that the images folder contains the exact file names (case-sensitive).
Example CSV:
uuid,photo_file
933da504-1d3a-455f-8e74-da853dbeea61,male33.jpg
4d0dd5c2-3bd1-4f65-a839-6bf8c9703bde,male2.jpg
dfcad164-49e9-459a-9396-eb93adf2b131,female1.jpg
90fe4e71-8c93-417e-8eb0-8f886b3335f2,female2.jpg
1da376b8-21ac-4992-9638-6160324c450e,male3.jpg
Folder Structure
- The folder containing the Python script should have the following structure:
-
bulk_upload_person_profile_photos.py
(your Python script) -
csv_files.csv
(the CSV file containing UUIDs and photo file names) -
images/
(folder containing the image files likemale33.jpg
,male34.jpg
, etc.)
-
Python Script
Below is the complete Python script. You can copy it and modify the parameters as needed.
# -*- coding: utf-8 -*-
"""
Created on Wed Jan 22 20:21:47 2025
@author: mlr
# DISCLAIMER:
# running this script/function means you will not blame the author if this breaks your data.
# This script/function is provided AS IS without warranty of any kind.
# There is no support for the script.
# This script has been tested with Pure 5.31 but might not work for future versions of Pure.
# This script is developed for Python version 3.11, and have not been tested with any other version of Python.
# Please make sure any Pure server you run this script against, has been backed up prior to running this script,
# to insure you can return to a working Pure if things fails.
"""
import os
import requests
import json
import csv
# API endpoint URLs
person_url = "https://YourPureURL/ws/api/persons/" # Replace with your actual person update URL
photo_url = "https://YourPureURL/ws/api/persons/file-uploads" # Replace with your actual photo upload URL
# Headers for the requests
headers = {
'api-key': 'YourAPIKeyHere', # Replace with your actual API key
'Accept': 'application/json'
}
# Get the current script directory
script_dir = os.path.dirname(os.path.abspath(__file__))
# Relative paths to the CSV file and the images folder
csv_file_path = os.path.join(script_dir, 'csv_files.csv')
photo_dir = os.path.join(script_dir, 'images')
# Ensure the images folder exists
if not os.path.exists(photo_dir):
print(f"❌ Error: Images folder not found at {photo_dir}")
exit(1)
# Ensure the CSV file exists
if not os.path.exists(csv_file_path):
print(f"❌ Error: CSV file not found at {csv_file_path}")
exit(1)
# Function to upload a photo and get its digest and metadata
def upload_photo(file_path):
with open(file_path, 'rb') as photo_file:
headers['Content-Type'] = 'image/jpeg'
response = requests.put(photo_url, headers=headers, data=photo_file)
if response.status_code == 200:
print(f"✅ Photo upload successful: {file_path}")
response_data = response.json()
return {
"digest": response_data.get("digest"),
"digestType": response_data.get("digestType"),
"size": response_data.get("size"),
"timestamp": response_data.get("timeStamp"),
"expire": response_data.get("expires"),
"key": response_data.get("key")
}
else:
print(f"❌ Photo upload failed: {file_path} with status code: {response.status_code}")
print(f"Response Body: {response.text}")
raise Exception(f"Photo upload failed with status code: {response.status_code}")
# Read the CSV file and process each row
with open(csv_file_path, newline='', encoding='utf-8') as csvfile:
csvreader = csv.DictReader(csvfile)
# Validate CSV headers
required_headers = {'uuid', 'photo_file'}
if not required_headers.issubset(csvreader.fieldnames):
print(f"❌ Error: CSV file must contain the following columns: {', '.join(required_headers)}")
exit(1)
for row in csvreader:
uuid = row.get('uuid', '').strip()
photo_file_name = row.get('photo_file', '').strip()
# Skip rows with missing UUID or photo filename
if not uuid or not photo_file_name:
print(f"⚠️ Skipping row due to missing data: {row}")
continue
photo_path = os.path.join(photo_dir, photo_file_name)
# Debugging: Print out the file path to verify
print(f"🔍 Checking file: {photo_path}")
if not os.path.exists(photo_path):
print(f"⚠️ Skipping {photo_file_name}, file not found at {photo_path}.")
continue
# Upload photo to get the digest and metadata
photo_metadata = upload_photo(photo_path)
# Prepare JSON payload for updating the person
json_payload = {
"profilePhotos": [
{
"fileName": photo_file_name,
"mimeType": "image/jpeg",
"uploadedFile": {
"digest": photo_metadata["digest"],
"digestType": photo_metadata["digestType"],
"size": photo_metadata["size"],
"timestamp": photo_metadata["timestamp"],
"expire": photo_metadata["expire"],
"key": photo_metadata["key"]
},
"type": {
"uri": "/dk/atira/pure/person/personfiles/portrait",
"term": {
"en_GB": "Some text"
}
},
"copyrightConfirmation": True,
"caption": {
"en_GB": f"Uploaded photo for {uuid}"
},
"altText": {
"en_GB": f"Profile photo for {uuid}"
},
"copyrightStatement": {
"en_GB": "Enter you own copyright statement for the photos here"
}
}
]
}
# Log JSON payload to check before sending
print(f"📤 Payload for updating person {uuid}: {json.dumps(json_payload, indent=2)}")
# Make the PUT request to update the person with the photo
person_update_url = f"{person_url}/{uuid}"
# Ensure the correct content type for the JSON payload
update_headers = headers.copy()
update_headers['Content-Type'] = 'application/json'
response = requests.put(person_update_url, headers=update_headers, data=json.dumps(json_payload))
# Log the response status, body, and headers
print(f"📬 Response Status Code: {response.status_code}")
print(f"📬 Response Headers: {response.headers}")
print(f"📬 Response Body: {response.text}")
if response.status_code == 200:
print(f"✅ Successfully updated person {uuid} with photo {photo_file_name}.")
else:
print(f"❌ Failed to update person {uuid} with photo {photo_file_name}. Status code: {response.status_code}")
Checkpoint validation process
✅ Handles missing uuid or photo_file in individual rows by skipping them.
✅ Prints warnings (⚠️) instead of crashing when files or data are missing.
✅ Uses structured logging (✅, ❌, 📤, 📬) to make debugging easier.
✅ Customization: Replace placeholders such as API URLs and keys with actual values.
Example Command for Running the Script
Once all files are in place, simply run the script:
python bulk_upload_person_profile_photos.py
Updated at February 13, 2025