Skip to main content

🐦 sapsucker

License: MIT Python Version PyPI version Unittests Linting Formatting Coverage

Typed Python wrapper for the SAP GUI Scripting API. For a Go client around the SAP ADT API, check adtler.

sapsucker gives you typed, IDE-friendly access to SAP GUI for Windows. Instead of working with raw COM objects and guessing method names, you get Python classes with autocomplete, type hints, and docstrings for every SAP GUI element.

Named after the sapsucker — a woodpecker that taps into trees to drink their sap. This library taps into SAP GUI to extract your data.

Quickstart

from sapsucker import SapGui

# Connect to running SAP GUI
app = SapGui.connect()
session = app.connections[0].sessions[0]

# Read session info
print(session.info.system_name)   # → "S4H"
print(session.info.user)          # → "DEVELOPER"

# Navigate to a transaction
session.find_by_id("wnd[0]/tbar[0]/okcd").text = "/nSE16"
session.find_by_id("wnd[0]").send_v_key(0)  # Enter

# Read the status bar
print(session.find_by_id("wnd[0]/sbar").text)

Why sapsucker?

  • Read a whole screen in one call — container.dump_tree() walks any screen recursively and returns typed ElementInfo, so you can discover element IDs instead of guessing them (the whole subtree in a single COM round trip on SAP GUI >= 7.70 PL3)
  • 40+ typed wrapper classes — GuiGridView.get_cell_value(), GuiTree.expand_node(), not generic element.read("cell", row, col)
  • IDE autocomplete & type hints on every method and property
  • 430+ unit tests, 50+ integration tests verified against real SAP S/4 HANA
  • API verified against the SAP GUI Scripting API 6.40 PDF (2969 pages)
  • MIT licensed — no GPL restrictions

Installation

pip install sapsucker

Prerequisites

  • SAP GUI for Windows (7.x or 8.x)
  • SAP GUI Scripting enabled — ask your SAP Basis team to set sapgui/user_scripting = TRUE in transaction RZ11, and enable scripting in your SAP GUI options (Customize Local Layout → Accessibility & Scripting)
  • Python 3.11+ on Windows

Usage Examples

Read an entire screen

Don't know a screen's element IDs? Walk it. dump_tree() recurses through every child container and returns validated ElementInfo objects — id, type, name, text, tooltips, accessibility text, geometry and more (sapsucker.models.ElementInfo).

from sapsucker import SapGui
from sapsucker.models import ElementInfo

app = SapGui.connect()
session = app.connections[0].sessions[0]

window = session.find_by_id("wnd[0]")

def walk(elements: list[ElementInfo], depth: int = 0) -> None:
    for element in elements:
        print("  " * depth, element.id, element.type, element.text)
        walk(element.children, depth + 1)   # ElementInfo nests its children

walk(window.dump_tree())                    # full depth by default (safety cap: 200)
# walk(window.dump_tree(max_depth=2))       # or bound it

On SAP GUI for Windows >= 7.70 PL3 dump_tree() uses GuiSession.GetObjectTree under the hood — the whole subtree in a single COM round trip — and falls back automatically to per-property reads on older releases.

Feeding a screen to an LLM? Call session.get_object_tree() directly and ask for only the properties you need instead of the full element record. Unlike dump_tree(), this call has no fallback — it raises on SAP GUI older than 7.70 PL3.

import json

# A JSON *string*, one COM call, only the properties you list
raw = session.get_object_tree("wnd[0]", props=["Id", "Type", "Text"])
tree = json.loads(raw)          # the queried element is tree["children"][0]

# props=None returns Id only — the cheapest possible screen dump
ids_only_json = session.get_object_tree("wnd[0]")

Read an ALV grid

from sapsucker import SapGui
from sapsucker.components.grid import GuiGridView

app = SapGui.connect()
session = app.connections[0].sessions[0]

# Find the grid on the current screen
grid = session.find_by_id("wnd[0]/shellcont/shell")

# Read ALL rows - to_dicts pages through the grid's loaded window.
# (A naive `for row in range(grid.row_count)` reads only the rows SAP GUI has
# transferred to the frontend and silently returns blank beyond them.)
for row_dict in grid.to_dicts():
    print("\t".join(row_dict.values()))

Navigate a tree control

from sapsucker.components.tree import GuiTree

tree = session.find_by_id("wnd[0]/shellcont/shell/shellcont[1]/shell/shellcont[2]/shell")

key = tree.top_node
print(tree.get_node_text_by_key(key))

if tree.is_folder(key):
    tree.expand_node(key)

Fill a form

# Set a text field value
session.find_by_id("wnd[0]/usr/ctxtRS38M-PROGRAMM").text = "RSPARAM"

# Press F8 (Execute)
session.find_by_id("wnd[0]").send_v_key(8)

Context manager

with SapGui.connect() as app:
    session = app.connections[0].sessions[0]
    print(session.info.user)
# All connections closed automatically

More examples

The examples/sapsucker/ directory contains complete runnable scripts, all tested against a real SAP system:

Monitoring a session while a human records

sapsucker.monitor samples a live session so a recording made with SAP GUI's own recorder (Alt+F12 → Script Recording and Playback) can be paired with timestamps. A recorded .vbs has none, so it cannot tell you where the person paused — which is usually where they were deciding — nor that they went back to re-check a field, nor whether the save actually worked.

pip install sapsucker[cli]

sapsucker-monitor -o timing.jsonl \
  --watch "wnd[0]/shellcont/shell:FirstVisibleRow"

Start it, start the recorder, do the task, stop both. --watch takes any element_id:ComProperty pair and is repeatable — the example above timestamps each individual ALV scroll.

A prebuilt Windows .exe is attached to each release for machines without a Python toolchain.

To start SAP GUI's recorder from the same process, pass --record:

sapsucker-monitor -o timing.jsonl --record journey3.vbs

The name must be letters and digits with at most one dot (SAP GUI rejects _, - and spaces); the .vbs lands in the SAP GUI Scripts folder, upper-cased, and the full path is printed. The recorder and the sampler start one after the other, so the first JSONL line is a "record_type": "header" record holding the sampler origin (origin_at), the recording_file and the measured recorder_skew in seconds; elapsed in every sample is measured from that origin. In record mode SAP GUI always shows the F4 help as a modal and disables drag & drop. Library equivalent: session.start_recording("journey3.vbs") / session.stop_recording().

As a library:

from sapsucker.monitor import SessionMonitor, Watch

monitor = SessionMonitor(session, watches=[Watch(element_id="wnd[0]/shellcont/shell", prop="FirstVisibleRow")])
for sample in monitor.samples():      # generator: the caller owns the loop, and the thread
    if sample.changed:
        print(sample.elapsed, sample.changed, sample.gap_since_change)

samples() never starts a thread. COM is STA, so a monitor loop occupies its thread for its whole lifetime — see the sapsucker.monitor module docstring.

Correlating a recording with its log

sapsucker-correlate joins a recorded .vbs, the monitor JSONL made while it was recorded and (optionally) an SRT narration transcript into one timestamped timeline.

pip install sapsucker[cli]

sapsucker-correlate journey3.vbs timing.jsonl -m timeline.md

-o sets the JSONL output (default timeline.jsonl), -t attaches an SRT transcript (SRT is the only supported format). When the log has a --record header, cue times are taken as relative to the recorder start and shifted by the measured recorder_skew (without a header the shift is 0, i.e. relative to the monitor origin); that the narration starts with the recorder is an unverified assumption. With -t, the CLI prints a one-line notice saying so and every JSONL record carries "transcript_origin_assumed": true. An SRT with no cues is rejected (exit 2) rather than treated as no transcript. Each recorded statement is timestamped by the first strategy that fits, and the strategy is reported per step: exact-focus, ddic-suffix, watch-run, modal-bracket, fingerprint-screen, fingerprint-title, recorder-boilerplate, or unmatched. Each step also carries flags such as keyboard-anchor, layout-sensitive, suffix-ambiguous, sub-interval-collapse and value-mismatch. Clock alignment is reported per run, not per step: a log without a --record header has no measured clock offset, so every JSONL record carries "clock_origin_assumed": true and "recorder_skew": null, and the markdown header states the assumption.

The matcher is heuristic. A prototype timestamped most statements of one real recording/log pair; that is a single pair, not an accuracy figure, so read the timeline (especially the flags column) before relying on it.

Architecture

sapsucker wraps the SAP GUI Scripting COM API as a hierarchy of typed Python classes:

GuiApplication
  └── GuiConnection
       └── GuiSession
            └── GuiMainWindow
                 ├── GuiToolbar
                 ├── GuiMenubar
                 ├── GuiStatusbar
                 └── GuiUserArea
                      ├── GuiTextField, GuiLabel, GuiButton, ...
                      ├── GuiTableControl (classic dynpro tables)
                      ├── GuiGridView (ALV grids)
                      ├── GuiTree (tree controls)
                      ├── GuiTabStrip → GuiTab
                      └── GuiAbapEditor / GuiTextedit

Elements are discovered via session.find_by_id(sap_id), which returns the correct typed wrapper automatically (e.g., GuiGridView for an ALV grid, GuiTree for a tree control). The factory dispatches on TypeAsNumber and SubType COM properties.

Thread Safety

COM objects use the Single-Threaded Apartment (STA) model. All calls to a given SAP GUI session must happen from the same thread that called pythoncom.CoInitialize(). See the _com.py module docstring for details and an asyncio.to_thread() example.

API Overview

Class / method Description
SapGui Entry point — SapGui.connect() returns GuiApplication
GuiApplication Root object, manages connections
GuiConnection A TCP connection to an SAP server
GuiSession A session (mode) within a connection
GuiMainWindow The main SAP window
GuiTextField Single-line input field
GuiButton Push button
GuiCheckBox Checkbox
GuiComboBox Dropdown list
GuiGridView ALV grid (most common data display)
GuiTableControl Classic dynpro table
GuiTree Tree control (simple, list, or column)
GuiAbapEditor ABAP source code editor
GuiStatusbar Status bar at bottom of window
.dump_tree() Method on any visual container (GuiVContainer) — recursive screen dump, returns list[ElementInfo]

Contributing

Contributions are welcome! Please open an issue first to discuss what you'd like to change.

For detailed setup instructions (uv dependency groups, CI, linting, formatting, etc.), see the Hochfrequenz Python Template Repository.

# Clone and install dev dependencies
git clone https://github.com/Hochfrequenz/sapsucker.git
cd sapsucker
uv sync --group dev

# Run unit tests (no SAP required, works on any OS)
uv run pytest unittests/ -v

Integration tests against real SAP

Integration tests run against a real SAP GUI system and are automatically skipped on machines without SAP access. To run them locally:

  1. SAP GUI for Windows must be running with scripting enabled
  2. Create a .env file with your SAP credentials:
    SAP_CONNECTION_NAME=your_connection
    SAP_USER=your_user
    SAP_PASSWORD=your_password
    SAP_MANDANT=your_client
    SAP_LANGUAGE=EN
    
  3. Run:
    uv run pytest unittests/ -k integration -v
    

Integration tests run by default on any local machine and are automatically skipped in CI (GitHub Actions). Set SAP_SKIP_INTEGRATION=1 to skip them locally. They cover SE80, SE16N, SE37, SE38, and SM37 — all read-only, no SAP data is modified.

License

MIT

Metadata

Release files for sapsucker 1.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sapsucker 1.6.0
File Size Uploaded
sapsucker-1.6.0.tar.gz 217.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sapsucker 1.6.0
File Interpreter ABI Platform
sapsucker-1.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 315.3 kB

Release files / sapsucker-1.6.0.tar.gz

Download URL sapsucker-1.6.0.tar.gz
Size 217.3 kB
Tags Source
SHA-256 checksum
How to use checksums
31816227504ef59e09bc752fd85e5b27b292dc85807dc2d21416d38d677b102b
BLAKE2b-256 checksum
How to use checksums
b692fd037c6c2476a0b6ff535842b460b5984bb21e62430a38b73836fd87ad09
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release files / sapsucker-1.6.0-py3-none-any.whl

Download URL sapsucker-1.6.0-py3-none-any.whl
Size 98.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
afcc942e84639b53dabf8e6fee6c7d21ae3830dccc300d396a24045d274d0d04
BLAKE2b-256 checksum
How to use checksums
4aa01ab6d7dfb88759056f504c6258985d6a6569d66e110862ec00fc8c38a2fc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.6.0 This release

2 release files

1.5.0

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page