Initialization

PyOData requires an external HTTP library which has API compatible with Session from Requests.

Get the service

Basic initialization which is going to work for everybody:

import pyodata
import requests

SERVICE_URL = 'http://services.odata.org/V2/Northwind/Northwind.svc/'

northwind = pyodata.Client(SERVICE_URL, requests.Session())

Get the service proxy client for an OData service requiring sap-client parameter

This is a sample when it is necessary to specify sap-client:

import pyodata
import requests

SERVICE_URL = 'http://services.odata.org/V2/Northwind/Northwind.svc/'

session = requests.Session()
param = {'sap-client': '510'}
session.params = param
northwind = pyodata.Client(SERVICE_URL, session)

Get the service proxy client for an OData service requiring authentication

Let’s assume you need to work with a service at the URL https://odata.example.com/Secret.svc and User ID is ‘MyUser’ with the password ‘MyPassword’.

import pyodata
import requests

SERVICE_URL = 'https://odata.example.com/Secret.svc'

session = requests.Session()
session.auth = ('MyUser', 'MyPassword')

theservice = pyodata.Client(SERVICE_URL, session)

Get the service proxy client for an OData service requiring Certificate authentication

Let’s assume your service requires certificate based authentication and you are able to export the certificate into the file mycert.p12. You need to split the certificate into public key, private key and certificate authority key.

The following steps has been verified on Fedora Linux and Mac OS.

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.pem

You can verify your steps by curl:

curl --key key_decrypt.pem --cacert ca.pem --cert client.pem -k 'SERVICE_URL/$metadata'

Python client initialization:

import 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)

For more information on client side SSL cerificationcas, please refer to this [gist](https://gist.github.com/mtigas/952344).

Get the service with local metadata

It may happen that you will have metadata document for your service downloaded in a local file and you will want to initialize the service proxy from this file. In such a case you can provide content of the file as the named argument metadata. Please, make sure you provide bytes and not str (required by the xml parser lxml).

import pyodata
import requests

SERVICE_URL = 'http://services.odata.org/V2/Northwind/Northwind.svc/'

with open('/the/file/path.xml', 'rb') as mtd_file:
    local_metadata = mtd_file.read()

northwind = pyodata.Client(SERVICE_URL, requests.Session(), metadata=local_metadata)

Dealing with errors during parsing metadata

In the case where you need to consume a service which has not fully valid metadata document and is not under your control, you can configure the metadata parser to try to recover from detected problems.

Parser recovery measures include actions such as using a stub entity type if the parser cannot find a referenced entity type. The stub entity type allows the parser to continue processing the given metadata but causes fatal errors when accessed from the client.

Class config provides easy to use wrapper for all parser configuration. These are:
  • XML namespaces
  • Parser policies (how parser act in case of invalid XML tag). We now support three types of policies:
    • Policy fatal - the policy raises exception and terminates the parser
    • Policy warning - the policy reports the detected problem, executes a fallback code and then continues normally
    • Policy ignore - the policy executes a fallback code without reporting the problem and then continues normally

Parser policies can be specified individually for each XML tag (See enum ParserError for more details). If no policy is specified for the tag, the default policy is used.

For parser to use your custom configuration, it needs to be passed as an argument to the client.

import pyodata
from pyodata.v2.model import PolicyFatal, PolicyWarning, PolicyIgnore, ParserError, Config
import requests

SERVICE_URL = 'http://services.odata.org/V2/Northwind/Northwind.svc/'

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)

Additionally, Schema class has Boolean atribute ‘is_valid’ that returns if the parser encountered errors. It’s value does not depends on used Parser policy.

northwind.schema.is_valid

Prevent substitution by default values

Per default, missing properties get filled in by type specific default values. While convenient, this throws away the knowledge of whether a value was missing in the first place. To prevent this, the class config mentioned in the section above takes an additional parameter, retain_null.

import pyodata
import requests

SERVICE_URL = 'http://services.odata.org/V2/Northwind/Northwind.svc/'

northwind = pyodata.Client(SERVICE_URL, requests.Session(), config=pyodata.v2.model.Config(retain_null=True))

unknown_shipped_date = northwind.entity_sets.Orders_Qries.get_entity(OrderID=11058,
                                                                     CompanyName='Blauer See Delikatessen').execute()

print(
    f'Shipped date: {"unknown" if unknown_shipped_date.ShippedDate is None else unknown_shipped_date.ShippedDate}')

Changing retain_null to False will print Shipped date: 1753-01-01 00:00:00+00:00.

Set custom namespaces (Deprecated - use config instead)

Let’s assume you need to work with a service which uses namespaces not directly supported by this library e. g. ones hosted on private urls such as customEdmxUrl.com and customEdmUrl.com:

import pyodata
import requests

SERVICE_URL = 'http://services.odata.org/V2/Northwind/Northwind.svc/'

namespaces = {
    'edmx': 'customEdmxUrl.com'
    'edm': 'customEdmUrl.com'
}

northwind = pyodata.Client(SERVICE_URL, requests.Session(), namespaces=namespaces)