AI/LLM-friendly Python client for SAP OData V2 and V4 APIs - single generic function for all operations
Project description
SAP OData Python
Simple, AI/LLM-friendly Python client for SAP OData V2 and V4 services.
Why This Library?
- SAP-First Design: Built specifically for SAP OData services (V2 Gateway & V4 RAP)
- AI/LLM Friendly: Single generic function design - perfect for AI agents and automation
- Simple API: One client, one method signature for all operations
- Automatic URL Building: Handles complex SAP URL patterns automatically
- Input Validation: Clear error messages for missing parameters
- Raw Responses: Returns actual API response - use helper methods to extract data
- Helper Methods:
get_value()andget_next_link()for easy data extraction
Installation
pip install sap-odata-python
Quick Start - SAP Systems
from sap_odata import ODataClient
# Connect to SAP system (sap_mode=True is the default)
client = ODataClient(
"https://sap-system.company.com:44300",
username="your_user",
password="your_password",
client="100"
)
# SAP OData V4 (RAP Services)
data = client.get(
service="zsd_my_service",
entity="MyEntity",
version="v4",
namespace="zsb_my_service", # Required for V4
filter="Status eq 'ACTIVE'",
top=10
)
# SAP OData V2 (Gateway Services)
data = client.get(
service="ZMY_SALESORDER_SRV",
entity="SalesOrderSet",
version="v2",
filter="Status eq 'OPEN'",
top=10
)
# Use helper methods to extract data
for item in client.get_value(data, "v2"):
print(item)
SAP URL Patterns (Handled Automatically)
The library automatically builds correct SAP URLs:
| Version | URL Pattern |
|---|---|
| V4 | /sap/opu/odata4/sap/{namespace}/srvd_a2x/sap/{service}/0001/{entity} |
| V2 | /sap/opu/odata/sap/{service}/{entity} |
You just provide service, entity, and namespace (for V4) - the library builds the full URL.
API Reference
ODataClient Constructor
client = ODataClient(
host, # SAP system URL (e.g., "https://sap.company.com:44300")
username="", # SAP username
password="", # SAP password
client="", # SAP client number (e.g., "100", "120")
sap_mode=True, # True for SAP systems (default), False for other OData services
verify_ssl=True, # SSL certificate verification
timeout=60 # Request timeout in seconds
)
Methods
| Method | Description |
|---|---|
get(service, entity, version, namespace, **params) |
Read data (GET) |
post(service, entity, data, version, namespace) |
Create record (POST) |
patch(service, entity, data, version, namespace) |
Update record (PATCH) |
delete(service, entity, version, namespace) |
Delete record (DELETE) |
metadata(service, version, namespace) |
Get service metadata (XML) |
get_value(response, version) |
Extract entity array from response |
get_next_link(response, version) |
Extract pagination URL from response |
Query Parameters (for GET)
| Parameter | Example | Description |
|---|---|---|
top |
top=10 |
Limit number of results |
skip |
skip=20 |
Skip records (pagination) |
filter |
filter="Price gt 100" |
OData filter expression |
select |
select="ID,Name,Price" |
Select specific fields |
expand |
expand="Customer,Items" |
Expand navigation properties |
orderby |
orderby="Name asc" |
Sort results |
SAP OData V4 (RAP Services)
client = ODataClient(
"https://sap-system.company.com:44300",
username="user",
password="pass",
client="100"
)
# Simple query
data = client.get(
service="zmy_product_api",
entity="Products",
version="v4",
namespace="zsb_product_api",
filter="ProductID eq '12345'"
)
# Complex nested $expand
data = client.get(
service="zmy_order_api",
entity="Orders",
version="v4",
namespace="zsb_order_api",
filter="OrderID eq '12345'",
expand="Customer($expand=Contacts),LineItems($expand=Product,Discounts($expand=Details)),Payments($expand=BankAccount)"
)
# Access nested data using helper method
for order in client.get_value(data, "v4"):
print(f"Order: {order['OrderID']}")
for line in order.get("LineItems", []):
print(f" Line Item: {line['ProductName']}")
SAP OData V2 (Gateway Services)
# Simple query
data = client.get(
service="ZMY_SALESORDER_SRV",
entity="SalesOrderSet",
version="v2",
top=10,
filter="Status eq 'OPEN'"
)
# Entity with key in path
data = client.get(
service="ZMY_CUSTOMER_SRV",
entity="CustomerSet(CustomerID='CUST001',Region='US')",
version="v2"
)
# Complex nested $expand (V2 style with /)
data = client.get(
service="ZMY_ORDER_SRV",
entity="OrderSet(OrderID='12345')",
version="v2",
expand="OrderToCustomer/CustomerToContacts,OrderToItems/ItemToProduct,OrderToItems/ItemToDiscounts,OrderToPayments/PaymentToBankAccount"
)
# V2 nested results are in "results" arrays
for order in client.get_value(data, "v2"):
print(f"Order: {order['OrderID']}")
for item in order.get("OrderToItems", {}).get("results", []):
print(f" Item: {item['ProductName']}")
Write Operations
# POST - Create
new_order = client.post(
service="ZMY_SALESORDER_SRV",
entity="SalesOrderSet",
data={"CustomerID": "CUST001", "Amount": 1000},
version="v2"
)
# PATCH - Update
client.patch(
service="ZMY_SALESORDER_SRV",
entity="SalesOrderSet('12345')",
data={"Status": "APPROVED"},
version="v2"
)
# DELETE
client.delete(
service="ZMY_SALESORDER_SRV",
entity="SalesOrderSet('12345')",
version="v2"
)
Get Service Metadata
# V4 metadata
xml = client.metadata(service="zmy_product_api", namespace="zsb_product_api")
# V2 metadata
xml = client.metadata(service="ZMY_SALESORDER_SRV", version="v2")
print(xml) # Returns XML string with entity definitions
Using with Non-SAP OData Services
For public OData services like Northwind or TripPin, set sap_mode=False:
from sap_odata import ODataClient
# Non-SAP OData service - set sap_mode=False
client = ODataClient("https://services.odata.org", sap_mode=False)
# Northwind V4
data = client.get("V4/Northwind/Northwind.svc", "Products", top=5)
# Northwind V2
data = client.get("V2/Northwind/Northwind.svc", "Products", version="v2", top=5)
# TripPin
data = client.get("TripPinRESTierService", "People", top=3)
data = client.get("TripPinRESTierService", "Airlines")
Note: sap_mode=True is the default since this library is designed for SAP systems.
Response Format
Responses are returned raw (as received from the API):
# V4 response format
{"@odata.context": "...", "value": [{...}, {...}], "@odata.nextLink": "..."}
# V2 response format
{"d": [{...}, {...}]} # or {"d": {"results": [...], "__next": "..."}}
Helper Methods
Use helper methods to extract data consistently:
# Extract entities from response
items = client.get_value(response, "v4") # Returns list
items = client.get_value(response, "v2") # Returns list
# Get pagination URL
next_url = client.get_next_link(response, "v4") # Returns URL or ""
next_url = client.get_next_link(response, "v2") # Returns URL or ""
Pagination Example
# Fetch all pages
all_items = []
response = client.get("ZMY_SRV", "ItemSet", version="v2", top=100)
while True:
all_items.extend(client.get_value(response, "v2"))
next_link = client.get_next_link(response, "v2")
if not next_link:
break
# Fetch next page using the full URL
response = client.session.get(next_link).json()
print(f"Total items: {len(all_items)}")
Error Handling
from sap_odata import ODataClient, ODataError, ODataConnectionError, ODataAuthError
try:
client = ODataClient("https://sap.company.com", "user", "pass", client="100")
data = client.get("zsd_my_service", "MyEntity", version="v4", namespace="zsb_my_service")
except ODataAuthError:
print("Authentication failed - check username/password")
except ODataConnectionError as e:
print(f"Connection failed: {e}")
except ODataError as e:
print(f"OData error: {e}")
Validation Errors
The library validates inputs and provides clear error messages:
# Missing namespace for V4
client.get("zsd_my_service", "MyEntity", version="v4")
# ODataError: Namespace is required for SAP OData V4 services.
# Invalid version
client.get("service", "entity", version="v3")
# ODataError: Invalid version 'v3'. Must be 'v2' or 'v4'
Context Manager
with ODataClient("https://sap-system.com", "user", "pass", client="100") as client:
data = client.get("ZMY_SALESORDER_SRV", "SalesOrderSet", version="v2")
# Session automatically closed
For AI/LLM Integration
This library is designed to be AI-friendly with a simple, consistent API:
# Single function signature for all SAP OData calls
client.get(
service="<service_name>",
entity="<entity_name>",
version="v2" | "v4",
namespace="<namespace>", # Required for V4
filter="<odata_filter>",
select="<fields>",
expand="<navigations>",
top=<number>
)
License
Apache 2.0
Links
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sap_odata_python-1.0.7.tar.gz.
File metadata
- Download URL: sap_odata_python-1.0.7.tar.gz
- Upload date:
- Size: 18.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bdda9d8aecddf78a8569c962852da4d9e8f7ef9f0747dc43daf1d3fbcf233c02
|
|
| MD5 |
9d81da0784fa48da245cc0b2bdea2136
|
|
| BLAKE2b-256 |
1307843aa498cbb6411367708e5119d0652f43decba4872dc536b37f838b22d8
|
File details
Details for the file sap_odata_python-1.0.7-py3-none-any.whl.
File metadata
- Download URL: sap_odata_python-1.0.7-py3-none-any.whl
- Upload date:
- Size: 12.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e42133729d5024962277358b1ad5dab7fe5606d66bf2bfeb8694ce2448de99ea
|
|
| MD5 |
e3eba93316fab9e1be2d82203b42aaa9
|
|
| BLAKE2b-256 |
40a9a189b7e8f2f3cd38b8e5c3063476fc21aa674466e09d8804598047b93232
|