Published at October 27th, 2025 Last updated 7 days ago

Bulk 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

 

Expertise level

Knowledge of Python is required. See also Python API Requests: Fundamental Coding Examples

 

Requirements

 

Setup Before Running the Script 


Ensure the following prerequisites are met:

  1. Python 3.x installed.
  2. The necessary libraries must be installed:
    • requests
    • json
    • csv
  3. The CSV file (csv_files.csv) containing person UUIDs and photo file names must be located in the same directory as the script.
  4. 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:

  1. uuid: The UUID of the person whose profile will be updated.
  2. photo_file: The name of the image file to be uploaded.
    1. 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 like male33.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