Overview of pyodata features and requirements
masterSupported Features
- OData V2
Requirements
- Python >= 3.10
repository·master·Indexed 42 days ago
https://github.com/sap/python-pyodataA Pythonic OData V2 client that abstracts protocol implementation details to communicate with OData services. It features a `pyodata.Client` for service interaction, support for batch requests and changesets, and a CLI tool for inspecting service metadata, entity sets, and function imports. Requires Python >= 3.10 and a session object compatible with requests.Session.
By default, PyOData fills missing properties with type-specific default values. To distinguish between a missing value and a default value, set retain_null=True in the Config object passed to the client. This ensures that missing properties are returned as None.
import pyodata
import requests
SERVICE_URL = 'http://services.odata.org/V2/Northwind/Northwind.svc/'
# Use retain_null=True to get None instead of default values for missing properties
config = pyodata.v2.model.Config(retain_null=True)
northwind = pyodata.Client(SERVICE_URL, requests.Session(), config=config)
# Example usage
entity = northwind.entity_sets.Orders_Qries.get_entity(OrderID=11058, CompanyName='Blauer See Delikatessen').execute()
print(f'Shipped date: {"unknown" if entity.ShippedDate is None else entity.ShippedDate}')When consuming services with invalid metadata, you can use the Config class to define how the parser handles errors. This is passed to the pyodata.Client via the config argument.
Parser Policies:
PolicyFatal: Raises an exception and terminates parsing (default).PolicyWarning: Reports the problem, executes fallback code, and continues.PolicyIgnore: Executes fallback code without reporting the problem and continues.You can specify policies for individual error types using the custom_error_policies dictionary and keys from the ParserError enum.
import pyodata
from pyodata.v2.model import PolicyFatal, PolicyWarning, PolicyIgnore, ParserError, Config
import requests
SERVICE_URL = 'http://services.odata.org/V2/Northwind/Northwind.svc/'
# Define custom namespaces and error handling
namespaces = {
'edmx': 'customEdmxUrl.com',
'edm': 'customEdmUrl.com'
}
custom_config = Config(
xml_namespaces=namespaces,
default_error_policy=PolicyFatal(),
custom_error_policies={
ParserError.ANNOTATION: PolicyWarning(),
ParserError.ASSOCIATION: PolicyIgnore()
})
northwind = pyodata.Client(SERVICE_URL, requests.Session(), config=custom_config)
# Check if the parser encountered any errors
print(northwind.schema.is_valid)$metadata, access the schema property on your Service instance. This returns a Schema object (from pyodata.v2.model) containing the parsed metadata of the OData service. This is useful for ensuring that references to properties, entities, and entity sets are valid.To use pyodata, import the module and instantiate a pyodata.Client. The constructor requires the OData service URL and a session object. The session object must implement an interface compatible with the Requests Session Object.
import requests
import pyodata
SERVICE_URL = 'http://services.odata.org/V2/Northwind/Northwind.svc/'
# Create instance of OData client
client = pyodata.Client(SERVICE_URL, requests.Session())Install or update the pyodata library using pip.
pip install -U pyodataFor services requiring certificate-based authentication, you must provide the certificate and key to the requests.Session object.
.p12 certificate into a public key (client.pem), a private key (key_decrypt.pem), and a CA certificate (ca.pem) using OpenSSL.session.verify to the CA certificate path.session.cert to a tuple of (client_cert_path, private_key_path).# Extracting components from a .p12 file
openssl pkcs12 -in mycert.p12 -out ca.pem -cacerts -nokeys
openssl pkcs12 -in mycert.p12 -out client.pem -clcerts -nokeys
openssl pkcs12 -in mycert.p12 -out key.pem -nocerts
openssl rsa -in key.pem -out key_decrypt.pemimport pyodata
import requests
SERVICE_URL = 'https://odata.example.com/Secret.svc'
session = requests.Session()
session.verify = 'ca.pem'
session.cert = ('client.pem', 'key_decrypt.pem')
client = pyodata.Client(SERVICE_URL, session)Because the create action uses the HTTP POST method, remote servers often require a CSRF token. When using the requests library, you should initialize a requests.Session, fetch the x-csrf-token from the service root using a HEAD request, and then update your session headers with that token before passing the session to the pyodata.Client.
import pyodata
import requests
SERVICE_URL = 'http://example.io/TheServiceRoot/'
session = requests.Session()
# Fetch the CSRF token
response = session.head(SERVICE_URL, headers={'x-csrf-token': 'fetch'})
token = response.headers.get('x-csrf-token', '')
session.headers.update({'x-csrf-token': token})
# Initialize the client with the prepared session
theservice = pyodata.Client(SERVICE_URL, session)Some SAP OData services return a 200 OK status but signal domain errors through the sap-message response header. Because standard error handlers do not inspect this header, you should use the sap_header_error_hook provided by pyodata.vendor.SAP.
This hook reads the sap-message header, parses it as JSON, and raises a pyodata.vendor.SAP.BusinessGatewayError if the severity field in the JSON is set to "error".
Pass sap_header_error_hook as the response_hook argument when initializing your pyodata.Client or pyodata.Service. The hook is stateless and safe for concurrent or async use.
Catch pyodata.vendor.SAP.BusinessGatewayError to handle these specific domain errors.
import pyodata
from pyodata.vendor.SAP import sap_header_error_hook
import requests
SERVICE_URL = 'https://example.com/sap/opu/odata/sap/ZMyService'
session = requests.Session()
client = pyodata.Client(SERVICE_URL, session, response_hook=sap_header_error_hook)
try:
result = client.entity_sets.Employees.get_entity(1).execute()
except pyodata.vendor.SAP.BusinessGatewayError as ex:
print(f"SAP domain error: {ex}")Pyodata supports three ways to apply filters to an entity set request:
.filter().GetEntitySetFilter (aliased as esf) to build filters using logical operators like esf.and_() and comparison operators..filter(). For complex patterns, use double-underscore syntax (e.g., __contains, __startswith).from pyodata.v2.service import GetEntitySetFilter as esf
# 1. String-based
request = northwind.entity_sets.Employees.get_entities().filter("FirstName eq 'John' and LastName eq 'Smith'")
# 2. Pythonic style
request = northwind.entity_sets.Employees.get_entities().filter(esf.and_(
request.FirstName == 'John',
request.LastName == 'Smith'
))
# 3. ORM style (simple)
request = northwind.entity_sets.Employees.get_entities().filter(FirstName="John", LastName="Smith")
# 3. ORM style (complex patterns)
request = northwind.entity_sets.Employees.get_entities().filter(FirstName__contains="oh", LastName__startswith="Smi")
for entity in request.execute():
print(entity.EmployeeID)PyOData uses the standard Python logging module. It does not define its own handlers, so you must configure the logging system yourself to see library output. Setting the root logger to DEBUG is the most effective way to see detailed activity.
import logging
logging.basicConfig()
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)