Uploading a chart Image to Slack with Python Slack-sdk from Allure

Pradap Pandiyan
6 min readMar 13, 2025

--

Overview

Slack provides several methods to display images in your workspace. You can:

  • Upload an image file directly to a channel to appear as a file attachment.
  • Embed an image in a message using image blocks when the image is hosted at a public URL.

This documentation focuses on uploading an image file directly using Slack’s API.

Prerequisites

Before you begin, ensure you have the following:

  • Create an Image for a report to send to Slack: I have used the allure report summary to generate an image from the summary file.

Here is the code to generate an Image from a summary file in the allure report.

import json
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np

# ---------------------------------------------------------
# 1. Load Data from summary.json
# ---------------------------------------------------------
with open("allure-report/widgets/summary.json", "r") as f:
data = json.load(f)

stats = data.get("statistic", {})
failed = stats.get("failed", 0)
broken = stats.get("broken", 0)
passed = stats.get("passed", 0)
skipped = stats.get("skipped", 0)
unknown = stats.get("unknown", 0)

outcomes = ["Failed", "Broken", "Passed", "Skipped", "Unknown"]
values = [failed, broken, passed, skipped, unknown]
total_tests = sum(values)
percentages = [(v / total_tests * 100) if total_tests else 0 for v in values]

# Define a consistent color scheme
colors = [
"#F44336", # Failed (red)
"#FFEB3B", # Broken (yellow)
"#4CAF50", # Passed (green)
"#9E9E9E", # Skipped (gray)
"#9C27B0" # Unknown (purple)
]

# ---------------------------------------------------------
# 2. Donut Chart with Leader Lines (Skipping Zero Counts)
# ---------------------------------------------------------
fig, ax = plt.subplots(figsize=(7, 5))
ax.axis("equal") # Ensure the pie is a circle

# Create the donut (outer radius=1, inner radius=0.65)
wedges, _ = ax.pie(
values,
colors=colors,
startangle=140,
wedgeprops=dict(width=0.35) # thickness of the donut
)

# Add leader lines for non-zero slices
for i, wedge in enumerate(wedges):
if values[i] == 0:
# Skip labeling zero slices
continue

angle_deg = 0.5 * (wedge.theta1 + wedge.theta2)
angle_rad = np.deg2rad(angle_deg)

# Draw line from radius=1 (donut edge) to radius=1.2 (label position)
outer_x = 1.0 * np.cos(angle_rad)
outer_y = 1.0 * np.sin(angle_rad)
text_x = 1.2 * np.cos(angle_rad)
text_y = 1.2 * np.sin(angle_rad)

label_text = f"{outcomes[i]} - {values[i]} ({percentages[i]:.0f}%)"

ax.annotate(
label_text,
xy=(outer_x, outer_y),
xytext=(text_x, text_y),
ha="center", va="center",
fontsize=9,
arrowprops=dict(arrowstyle="-", color=colors[i])
)

# Legend on the right (show outcome & count)
legend_patches = []
for outcome, count, color in zip(outcomes, values, colors):
legend_patches.append(mpatches.Patch(color=color, label=f"{outcome} ({count})"))
ax.legend(
handles=legend_patches,
loc="center left",
bbox_to_anchor=(1.05, 0.5),
title="Outcomes"
)

# Add center label
ax.text(0, 0, "Allure Summary", ha="center", va="center", fontsize=12, fontweight="bold")

plt.title("Donut Chart with Leader Lines", loc="left", fontsize=12)
plt.tight_layout()
plt.savefig("donut_leader_chart.png", dpi=200)
plt.close()

# ---------------------------------------------------------
# 3. Bar Chart of the Same Data
# ---------------------------------------------------------
fig, ax = plt.subplots(figsize=(7, 5))

x_pos = np.arange(len(outcomes))
bars = ax.bar(x_pos, values, color=colors)

# Label each bar with the count
for i, bar in enumerate(bars):
height = bar.get_height()
ax.text(
bar.get_x() + bar.get_width() / 2,
height + 0.1,
str(values[i]),
ha="center",
va="bottom",
fontsize=9
)

ax.set_xticks(x_pos)
ax.set_xticklabels(outcomes)
ax.set_ylabel("Count")
ax.set_title("Bar Chart of Allure Results")

# Legend on the right (show outcome & count)
legend_patches = [
mpatches.Patch(color=c, label=f"{l} ({v})")
for l, v, c in zip(outcomes, values, colors)
]
ax.legend(
handles=legend_patches,
loc="center left",
bbox_to_anchor=(1.05, 0.5),
title="Outcomes"
)

plt.tight_layout()
plt.savefig("bar_chart.png", dpi=200)
plt.close()

print("Generated 'donut_leader_chart.png' and 'bar_chart.png'.")
  • A Slack App & API Token:
    Create a Slack app on Slack API and install it to your workspace.
  • For file uploads, your token should have the necessary scope, typically files:write (and chat:write if you’re posting messages).
  • It is recommended to use a bot token (starting with xoxb-).
  • Python and Slack SDK:
    If you plan to use Python, install the Slack SDK:
pip install slack_sdk
  • Environment Variable (optional but recommended):
    Instead of hardcoding your token, store it in an environment variable (e.g., SLACK_BOT_TOKEN).
  • File to Upload:
    Have the image (or any file) you wish to upload available on your local drive.

API Endpoints for File Uploads

files.upload vs. files_upload_v2

  • files.upload:
    The original endpoint for uploading files. Some users may experience timeouts or stability issues with large files.
  • files_upload_v2:
    The recommended and more stable endpoint for file uploads. It works similarly to files.upload but is optimized for better performance.

For this documentation, we’ll use files_upload_v2.

The Upload Process Explained

Step 1: Authentication

  • Token Passing:
    Your Slack API token must be included in your API calls. When using the Python SDK, you initialize the WebClient with your token.
  • Environment Variable:
    For security, store your token as an environment variable (SLACK_BOT_TOKEN) and retrieve it in your script.

Step 2: Channel Membership

  • Ensure the Bot is in the Channel:
    The bot must be a member of the channel to which you wish to upload the file. If it isn’t, you can use the conversations.join method to have the bot join the channel automatically.

Step 3: Uploading the File

Using files_upload_v2:
Pass the file path, title, channel ID, and an optional initial comment to the method. The SDK handles the multipart form data required to send the file.

Step 4: Error Handling

  • Common Errors:
  • not_authed: Authentication failed because the token wasn’t properly passed.
  • not_in_channel: The bot isn’t a member of the target channel.
  • Resolution:
  • Double-check your token and its scopes.
  • Ensure your bot has joined the channel before attempting the upload.

Full Code Example

Below is a complete Python script that demonstrates how to upload an image using the files_upload_v2() endpoint.

import logging
import os
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
# Set up basic logging to output info and errors
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Retrieve your Slack Bot token from an environment variable
slack_token = os.environ.get("SLACK_BOT_TOKEN")
if not slack_token:
raise ValueError("No Slack token found. Please set the SLACK_BOT_TOKEN environment variable.")
# Instantiate the Slack WebClient with your token
client = WebClient(token=slack_token)
# Specify the channel ID and file details
channel_id = "C3UKJTQAC" # Replace with your target channel ID
file_path = "test.pdf" # Replace with your file path; can be an image like "image.jpg"
try:
# Ensure the bot is a member of the channel (optional if already a member)
join_response = client.conversations_join(channel=channel_id)
logger.info("Joined channel response: %s", join_response)
except SlackApiError as join_error:
# If already in the channel, ignore the "already_in_channel" error.
if join_error.response.get("error") != "already_in_channel":
logger.warning("Error joining channel: %s", join_error.response.get("error"))
try:
# Upload the file using files_upload_v2
response = client.files_upload_v2(
file=file_path, # Path to your image or file
title="Test upload",
channel=channel_id,
initial_comment="Here is the latest version of the file!",
)
logger.info("File uploaded successfully: %s", response)
except SlackApiError as upload_error:
logger.error("Error uploading file: %s", upload_error.response.get("error"))

How to Run This Script

  1. Set the Environment Variable:
    In your terminal, run:
export SLACK_BOT_TOKEN="xoxb-your-valid-bot-token"
  1. Run the Script:
    Execute the script:
python upload_image.pypy

This script will:

  • Retrieve your Slack token securely.
  • Join the specified channel (if not already a member).
  • Upload the file (image or any file) to the channel using the files_upload_v2() method.
  • Log the responses and errors for troubleshooting.

6. Additional Considerations

  • Token Management:
    Never hardcode tokens in your source code. Use environment variables or secret management tools.
  • File Size & Stability:
    For larger files, files_upload_v2() is recommended due to improved stability.
  • Error Handling:
    Always implement error handling to manage common issues like authentication failures or channel membership problems.
  • Permissions & Scopes:
    Confirm your Slack app has the required scopes such as files:write and chat:write.

7. Alternative: Embedding Images in Messages

If your image is hosted online and you simply want to display it in a message, you can use the chat.postMessage endpoint with an image block. This method does not upload a file to Slack; it only embeds the image in your message.

Example payload:

{
"channel": "CHANNELID",
"blocks": [
{
"type": "image",
"image_url": "https://example.com/path/to/image.jpg",
"alt_text": "An image description"
}
]
}

This documentation provides a comprehensive overview of uploading an image to Slack via its API. Whether you need to upload files directly or embed images in messages, the steps and code examples above should help you get started. If you have further questions or run into issues, consult the Slack API documentation for additional details and troubleshooting tips.

Feel free to hit clap if you like the content. Happy Automation Testing :) Cheers. 👏

--

--

Pradap Pandiyan
Pradap Pandiyan

Written by Pradap Pandiyan

I’m a passionate QA Engineer. I’m a motovlogger, content creator, off-roader and freelancer. Buy me a coffee here https://www.buymeacoffee.com/pradappandiyan

No responses yet