PyOData requires an external HTTP library which has API compatible with Session from Requests.
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())
Initialization of the session instance is dependent on particular library, but also must have API compatible Session from Requests.
import httpx
import aiohttp
SERVICE_URL = 'http://services.odata.org/V2/Northwind/Northwind.svc/'
service_httpx = await pyodata.Client.build_async_client(SERVICE_URL, httpx)
service_aiohttp = await pyodata.Client.build_async_client(SERVICE_URL, aiohttp.ClientSession())
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)
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)
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).
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)
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
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.
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)