Browse our Products

Aspose.Slides for Python via .NET 26.8 Release Notes

New Features and Enhancements

KeySummaryCategoryRelated Documentation
SLIDESNET-43212Adding comment to table results in adding comment to slideEnhancementhttps://docs.aspose.com/slides/net/presentation-comments/
SLIDESNET-45459Implement export of chart objects to Markdown formatFeature
SLIDESNET-45461Implement export of SmartArt objects to Markdown formatFeature
SLIDESNET-45463Add support for rendering an image of an individual paragraphFeature

Other Improvements and Changes

KeySummaryCategoryRelated Documentation
SLIDESPYNET-364Use Aspose.Slides for Net 26.8 featuresEnhancementhttps://releases.aspose.com/slides/net/release-notes/2026/aspose-slides-for-net-26-8-release-notes/
SLIDESPYNET-345.NET Core 3.1 EOLInvestigation
SLIDESPYNET-335Conflict between Aspose.Slides and Aspose.Cells for Python due to aspose.pydrawing native extension overwriteBug
SLIDESPYNET-351Upgrade Aspose.Slides packages to .NET 8+ runtimeFeature
SLIDESPYNET-290Support the ARM architecture on LinuxFeature
SLIDESPYNET-329Support OpenSSL package to 3.x (openssl3 lib)Feature
SLIDESPYNET-312Import conflict: Aspose.Slides 25.9 & Aspose.Words 25.10Investigation
SLIDESPYNET-227Support handling exceptions like in .NETFeature
SLIDESPYNET-251Inquiry about support for .NET runtime newer than .NET Core 3.1Investigation
SLIDESPYNET-292CVE-2024-0057 vulnerabilityEnhancement

Read this page if your code worked on 26.7 and stopped working on 26.8.

Version 26.8 replaces the engine that connects Python to .NET. The drawing primitives moved into the aspose.slides module. Jump straight to I Have an Error if you have an issues after upgrade.

Types That Moved

TypeBefore 26.826.8 and later
Pointaspose.pydrawing.Pointaspose.slides.Point
PointFaspose.pydrawing.PointFaspose.slides.PointF
Sizeaspose.pydrawing.Sizeaspose.slides.Size
SizeFaspose.pydrawing.SizeFaspose.slides.SizeF
Rectangleaspose.pydrawing.Rectangleaspose.slides.Rectangle
RectangleFaspose.pydrawing.RectangleFaspose.slides.RectangleF
Coloraspose.pydrawing.Coloraspose.slides.Color

I Have an Error

Find your traceback in the left column.

ErrorCauseFix
AttributeError: module 'aspose.pydrawing' has no attribute 'Color' (or Point, Rectangle, …)The package is 26.8, the code still points at the old moduleUpdating Your Code
ImportError: cannot import name 'Color' from 'aspose.pydrawing'Same cause, from-import formUpdating Your Code
ImportError: cannot import name 'Color' from 'aspose.slides'The code was migrated but the package is still 26.7 or olderpip install --upgrade aspose.slides
ModuleNotFoundError: No module named 'aspose.pydrawing'Old aspose.pydrawing side module still importingUpdating Your Code
NameError: name 'slides' is not definedA migration script rewrote a reference in a scope where aspose.slides is not importedAdd the import of aspose.slides to that scope
TypeError on a color or point argumentA value from aspose.pydrawing is being passed to the new APIBoth sides must come from aspose.slides

The One-Minute Fix

If you only have a handful of call sites, this is the whole migration:

Changes in code with standard imports
# Before
import aspose.pydrawing as drawing

shape.fill_format.solid_fill_color.color = drawing.Color.red
origin = drawing.Point(10, 20)

# After
import aspose.slides as slides

shape.fill_format.solid_fill_color.color = slides.Color.red
origin = slides.Point(10, 20)

The from-import form:

Changes in code with from-imports
# Before
from aspose.pydrawing import Color, Point

# After
from aspose.slides import Color, Point

Names, arguments and behaviour are unchanged. Only the module differs.

About the Pydrawing Module

aspose.pydrawing was never a product you install. It was a module that shipped alongside Aspose Python via .NET packages, exposing a small set of .NET drawing types to Python. It never appeared in your requirements.txt, and there is nothing to add or remove there now.

Its entire contents were the seven types listed above. The new engine projects those types directly into aspose.slides, so the module has no remaining purpose.

The practical consequences:

  • pip install cannot fix a missing aspose.pydrawing. There is no package by that name to install. If you see ModuleNotFoundError, the fix is to migrate the code, not the environment.
  • You do not need to keep the module around for anything. There are no leftover types that only exist there.
  • Any surviving reference is a bug. After migration there should be no aspose.pydrawing.

Updating Your Code

Pick the approach that matches the size of your code base.

Option 1: Find and Replace

For a few files. Search for aspose.pydrawing and change the module to aspose.slides on any line that uses any of moved types. Leave the other lines alone.

Option 2: Shell Command

Because the module has no contents other than the moved types, you can rename the module itself. This handles every import form at once - including aliases and as renames - with no list of type names to keep in sync and no ordering pitfalls.

Linux:

Simple replacement on Linux/MacOS
grep -rlZ --include='*.py' 'aspose\.pydrawing' . \
  | xargs -0 -r sed -i.bak 's/aspose\.pydrawing/aspose.slides/g'

On macOS use sed -i '' instead of sed -i.bak, or install GNU sed as gsed.

Windows (PowerShell):

Simple replacement on Windows
Get-ChildItem -Recurse -Filter *.py | ForEach-Object {
  $t = Get-Content $_ -Raw
  $new = $t -replace 'aspose\.pydrawing', 'aspose.slides'
  if ($new -ne $t) {
    Copy-Item $_.FullName "$($_.FullName).bak"
    Set-Content $_.FullName $new -NoNewline
    $_.FullName
  }
}

To undo on Linux:

Restore on Linux
find . -name '*.py.bak' -exec sh -c 'mv "$1" "${1%.bak}"' _ {} \;

To undo on Windows (PowerShell):

Restore on Windows
Get-ChildItem -Recurse -Filter *.py.bak | ForEach-Object {
  Move-Item $_.FullName ($_.FullName -replace '\.bak$', '') -Force
}

Two things to be aware of. This is a plain text replacement, so it also rewrites occurrences inside strings, comments and docstrings - review the diff. And an aliased import becomes import aspose.slides as drawing, which works correctly but leaves a misleading alias name; rename it if you care about readability.

Option 3: Python Script, Text-Based

The same rename, portable across Linux, macOS and Windows. Runs as a preview by default.

Script migrate.py
import sys
from pathlib import Path

W = "--write" in sys.argv
B = "--backup" in sys.argv
ROOT = next((a for a in sys.argv[1:] if not a.startswith("-")), None)

if ROOT is None:
    sys.exit(f"usage: python {Path(sys.argv[0]).name} <path> [--write] [--backup]")

root = Path(ROOT)
if not root.exists():
    sys.exit(f"no such path: {root}")

files = [root] if root.is_file() else root.rglob("*.py")
changed = 0

for p in files:
    if {".venv", "venv", "__pycache__", ".git"} & set(p.parts):
        continue
    s = p.read_text(encoding="utf-8")
    n = s.replace("aspose.pydrawing", "aspose.slides")
    if n == s:
        continue
    changed += 1
    print(("wrote " if W else "would change ") + str(p))
    if W:
        if B:
            p.with_suffix(p.suffix + ".bak").write_text(s, encoding="utf-8")
        p.write_text(n, encoding="utf-8")

print(f"{changed} file(s) {'changed' if W else 'to change'}"
      + ("" if W or not changed else "; rerun with --write to apply"))

Run python migrate.py to preview, then python migrate.py --write [--backup]. Same caveat as above: it does not distinguish code from strings and comments. Files could be restored by using undo script from previous chapter.

Option 4: Python Script, AST-Based

Recommended for larger code bases. This version parses each file, so it never touches strings, comments or docstrings, and it handles every import form: import aspose.pydrawing, import aspose.pydrawing as X, from aspose.pydrawing import Color, from aspose.pydrawing import Color as C, and multi-line parenthesized imports. Because the module has no remaining contents, the script deletes every aspose.pydrawing import it finds once the references are repointed.

Where it cannot be certain, it prints a warning and changes nothing rather than guessing.

Script migrate_advanced.py
import ast, sys
from pathlib import Path

MOD, DST = "aspose.pydrawing", "aspose.slides"
W = "--write" in sys.argv
B = "--backup" in sys.argv
ROOT = next((a for a in sys.argv[1:] if not a.startswith("-")), None)

if ROOT is None:
    sys.exit(f"usage: python {Path(sys.argv[0]).name} <path> [--write] [--backup]")

root = Path(ROOT)
if not root.exists():
    sys.exit(f"no such path: {root}")

files = [root] if root.is_file() else root.rglob("*.py")
changed = 0


def chain(n):
    p = []
    while isinstance(n, ast.Attribute):
        p.append(n.attr)
        n = n.value
    return ".".join(reversed(p + [n.id])) if isinstance(n, ast.Name) else None


def fix(src):
    tree = ast.parse(src)
    off, o = [], 0
    for l in src.encode().splitlines(keepends=True):
        off.append(o)
        o += len(l)
    off.append(o)
    edits = []

    for n in ast.walk(tree):
        # import aspose.pydrawing [as X]  /  from aspose.pydrawing import ...
        # The module name is renamed in place, so any alias stays bound as before.
        if (isinstance(n, ast.Import) and any(a.name == MOD for a in n.names)) or \
           (isinstance(n, ast.ImportFrom) and n.module == MOD):
            s, e = off[n.lineno - 1], off[n.end_lineno - 1] + n.end_col_offset
            edits.append((s, e, src.encode()[s:e].decode().replace(MOD, DST)))
        # Any expression referring to the module, including bare `fn(aspose.pydrawing)`.
        elif isinstance(n, ast.Attribute) and chain(n) == MOD:
            edits.append((off[n.lineno - 1] + n.col_offset,
                          off[n.end_lineno - 1] + n.end_col_offset, DST))

    b = src.encode()
    for s, e, r in sorted(edits, reverse=True):  # back to front keeps offsets valid
        b = b[:s] + r.encode() + b[e:]
    return b.decode()


for p in files:
    if {".venv", "venv", "__pycache__", ".git"} & set(p.parts):
        continue
    s = p.read_text(encoding="utf-8")
    try:
        n = fix(s)
    except SyntaxError as e:
        print(f"skipped {p}: {e}")
        continue
    if n != s:
        print(("wrote " if W else "would change ") + str(p))
        if W:
            if B:
                p.with_suffix(p.suffix + ".bak").write_text(s, encoding="utf-8")
            p.write_text(n, encoding="utf-8")

Run python migrate_advanced.py src/ first and read the warnings, then python migrate_advanced.py src/ --write [--backup]. The script is idempotent: running it a second time on already migrated code changes nothing. Files could be restored by using undo script from pre-previous chapter.

A Note on Aliases

Both the shell command and the AST script rename the module while leaving the alias alone:

Alias renaming
# Before
import aspose.pydrawing as drawing
color = drawing.Color.red

# After
import aspose.slides as drawing
color = drawing.Color.red

This is correct and works in any scope. Because the alias is left untouched, an import inside a function body migrates exactly like one at module level:

Not readable code after migration
def get_red_color():
    import aspose.slides as drawing    # was aspose.pydrawing
    return drawing.Color.red           # unchanged

No new name is introduced, so there is nothing that can fall out of scope. The only downside is a misleading name: an alias called drawing now points at aspose.slides. Rename it if you care about readability, or make the intent explicit by converting to the plain form:

Readable code after migration
import aspose.slides as slides
color = slides.Color.red

Documentation

The changes described above are covered in detail in Migrate to the New Engine.

The article explains what changed and why, lists the errors you may encounter with their fixes, and provides ready-to-run scripts that apply the migration across an entire code base.

OpenSSL 3 is supported

OpenSSL 3 is now supported on Windows, Linux, macOS (both x86-64 and ARM64). If both OpenSSL 1.1.1 and 3.x are present on the host, the 3.x library is loaded by default.

Linux ARM64 platform is supported

We are pleased to announce the release of the ARM64 edition of Aspose.Slides for Python via .NET for Linux for ARM-based platforms. This architecture powers a growing share of modern cloud and edge infrastructure - from AWS Graviton and Ampere-based servers to single-board devices - delivering strong performance with excellent energy efficiency.

Aspose.Slides for Python on Linux ARM64 offers the same features as Aspose.Slides for Python on Windows (they share the same documentation and API reference). For more information on Aspose.Slides capabilities, see Features Overview.

Public API Changes

Supported handling exceptions like in .NET

Errors raised by Aspose.Slides now reach Python as typed exceptions instead of a generic RuntimeError. Every exception type of the .NET API has a Python counterpart with the same name and the same inheritance, so try/except blocks is similar to .NET ones:

from aspose.slides import Presentation, PptxReadException, InvalidPasswordException

try:
    with Presentation("report.pptx") as pres:
        pres.save("report.pdf", SaveFormat.PDF)
except InvalidPasswordException:
    print("The presentation is password-protected.")
except PptxReadException as e:
    print("Cannot read the file:", e)

There is scheme with some of existing Aspose.Slides exceptions:

Exception
└── AsposeSlidesException
    ├── InvalidPasswordException
    ├── SlidesAIAgentException
    ├── AxesCompositionNotCombinableException
    ├── CannotCombine2DAnd3DChartsException
    ├── OdpException
    │   └── OdpReadException
    ├── OOXMLException
    │   ├── OOXMLCorruptFileException
    │   └── PptxException
    │       ├── PptxReadException
    │       │   └── PptxUnsupportedFormatException
    │       └── PptxEditException
    │           ├── CellCircularReferenceException
    │           ├── CellInvalidFormulaException
    │           ├── CellInvalidReferenceException
    │           └── CellUnsupportedDataException
    └── PptException
        ├── PptEditException
        └── PptReadException
            ├── PptCorruptFileException
            └── PptUnsupportedFormatException

except AsposeSlidesException: is the catch-all for any failure coming from the library - including the .NET framework errors listed in the next section, which are made to derive from it as well.

.NET exceptions map to Python built-ins

Errors that .NET raises from the BCL rather than from Aspose.Slides arrive as the idiomatic Python type, so except FileNotFoundError: works where you would have written catch (FileNotFoundException):

.NET exceptionPython exception
System.IO.FileNotFoundException, System.IO.DirectoryNotFoundExceptionFileNotFoundError
System.ArgumentException, System.ArgumentNullException, System.ArgumentOutOfRangeExceptionValueError
System.InvalidOperationException, System.NullReferenceExceptionRuntimeError
System.NotSupportedExceptionNotImplementedError
System.UnauthorizedAccessExceptionPermissionError
System.OutOfMemoryExceptionMemoryError
System.OverflowExceptionOverflowError
System.TypeLoadExceptionImportError
any other .NET exceptionAsposeSlidesException

Errors detected before the call reaches .NET

Argument problems are reported by the Python layer itself, with the Python type that fits the mistake - no round trip to .NET, and no RuntimeError for what is really a programming error:

SituationException
No overload matches the supplied arguments, or an argument has the wrong typeTypeError (message lists the candidate overloads)
A value is out of the allowed range - e.g. Color.from_argb(300, 204, 102, 0), ShapeType(9999)ValueError
Index outside a collection - pres.slides[999]IndexError
Unknown attribute or method on a wrapper objectAttributeError

Support for rendering an image of an individual paragraph

The new get_image methods have been added to the IParagraph interface and Paragraph class. These methods allow you to render a paragraph as an image.

Usage examples

The following example shows how to render each paragraph in all AutoShapes on a slide as an image with custom scaling:

import aspose.slides as slides

with slides.Presentation("sample.pptx") as pres:
    slide = pres.slides[0]
    shape_index = 0

    for shape in slide.shapes:
        shape_index += 1

        if not isinstance(shape, slides.AutoShape) or shape.text_frame is None:
            continue

        paragraph_index = 0
        for paragraph in auto_shape.text_frame.paragraphs:
            paragraph_index += 1

            with paragraph.get_image(2, 2) as paragraph_image:
                paragraph_image.save(str.format("shape{0}_paragraph{1}.png", shape_index, paragraph_index))

The following example shows how to render each paragraph in a table:

import aspose.slides as slides

with slides.Presentation("sample.pptx") as pres:
    slide = pres.slides[0]
    table = slide.shapes[0]
    paragraph_index = 0

    for row_idx in range(len(table.rows)):
        for col_idx in range(len(table.cols)):
            cell = table.rows[row_idx][col_idx]
            if cell.text_frame is None:
                continue

            for paragraph in cell.text_frame.paragraphs:
                paragraph_index += 1

                with paragraph.get_image(2, 2) as paragraph_image:
                    paragraph_image.save(str.format("paragraph{0}.png", paragraph_index))