Blog
Microsoft Dataverse is a cloud-based data storage and management platform, often used with PowerApps and Dynamics 365.
Integrating Dataverse with Python via Zato enables automation, API orchestration, and seamless CRUD (Create, Read, Update, Delete) operations on any Dataverse object.
Below, you'll find practical code examples for working with Dataverse from Python, including detailed comments and explanations. The focus is on the "accounts" entity, but the same approach applies to any object in Dataverse.
The main service class configures the Dataverse client and retrieves all accounts. Both the handle
and get_accounts
methods are shown together for clarity.
# -*- coding: utf-8 -*-
# Zato
from zato.common.typing_ import any_
from zato.server.service import DataverseClient, Service
class MyService(Service):
def handle(self):
# Set up Dataverse credentials - in a real service,
# this would go to your configuration file.
tenant_id = '221de69a-602d-4a0b-a0a4-1ff2a3943e9f'
client_id = '17aaa657-557c-4b18-95c3-71d742fbc6a3'
client_secret = 'MjsrO1zc0.WEV5unJCS5vLa1'
org_url = 'https://org123456.api.crm4.dynamics.com'
# Build the Dataverse client using the credentials
client = DataverseClient(
tenant_id=tenant_id,
client_id=client_id,
client_secret=client_secret,
org_url=org_url
)
# Retrieve all accounts using a helper method
accounts = self.get_accounts(client)
# Process the accounts as needed (custom logic goes here)
pass
def get_accounts(self, client:'DataverseClient') -> 'any_':
# Specify the API path for the accounts entity
path = 'accounts'
# Call the Dataverse API to retrieve all accounts
response = client.get(path)
# Log the response for debugging/auditing
self.logger.info(f'Dataverse response (get accounts): {response}')
# Return the API response to the caller
return response
{'@odata.context': 'https://org1234567.crm4.dynamics.com/api/data/v9.0/$metadata#accounts',
'value': [{'@odata.etag': 'W/"11122233"', 'territorycode': 1,
'accountid': 'd92e6f18-36fb-4fa8-b7c2-ecc7cc28f50c', 'name': 'Zato Test Account 1',
'_owninguser_value': 'ea4dd84c-dee6-405d-b638-c37b57f00938'}]}
Let's check more examples - you'll note they all follow the same pattern as the first one.
def get_account_by_id(self, client:'DataverseClient', account_id:'str') -> 'any_':
# Construct the API path using the account's GUID
path = f'accounts({account_id})'
# Call the Dataverse API to fetch the account
response = client.get(path)
# Log the response for traceability
self.logger.info(f'Dataverse response (get account by ID): {response}')
# Return the fetched account
return response
def get_account_by_name(self, client:'DataverseClient', account_name:'str') -> 'any_':
# Construct the API path with a filter for the account name
path = f"accounts?$filter=name eq '{account_name}'"
# Call the Dataverse API with the filter
response = client.get(path)
# Log the response for auditing
self.logger.info(f'Dataverse response (get account by name): {response}')
# Return the filtered account(s)
return response
def create_account(self, client:'DataverseClient') -> 'any_':
# Specify the API path for account creation
path = 'accounts'
# Prepare the data for the new account
account_data = {
'name': 'New Test Account',
'telephone1': '+1-555-123-4567',
'emailaddress1': 'hello@example.com',
'address1_city': 'Prague',
'address1_country': 'Czech Republic',
}
# Call the Dataverse API to create the account
response = client.post(path, account_data)
# Log the response for traceability
self.logger.info(f'Dataverse response (create account): {response}')
# Return the API response
return response
def update_account(self, client:'DataverseClient', account_id:'str') -> 'any_':
# Prepare the data to update
update_data = {
'name': 'Updated Account Name',
'telephone1': '+1-555-987-6543',
'emailaddress1': 'hello2@example.com',
}
# Call the Dataverse API to update the account by ID
response = client.patch(f'accounts({account_id})', update_data)
# Log the response for auditing
self.logger.info(f'Dataverse response (update account): {response}')
# Return the updated account response
return response
def delete_account(self, client:'DataverseClient', account_id:'str') -> 'any_':
# Call the Dataverse API to delete the account
response = client.delete(f'accounts({account_id})')
# Log the response for traceability
self.logger.info(f'Dataverse response (delete account): {response}')
# Return the API response
return response
A detail to note when working with Dataverse APIs is that the names you see in the PowerApps or Dynamics UI are not always the same as the paths expected by the API. For example:
This pattern applies to all Dataverse objects: always check the API documentation or inspect the metadata to determine the correct entity path.
While the examples above focus on the "accounts" entity, the same approach applies to any object in Dataverse: contacts, leads, opportunities, custom tables, and more. Simply adjust the API path and payload as needed.
With Zato and Python, you get full CRUD (Create, Read, Update, Delete) capability for any Dataverse entity. The methods shown above can be adapted for any object, allowing you to automate, integrate, and orchestrate data flows across your organization.
This article has shown how to connect to Microsoft Dataverse from Python using Zato, perform CRUD operations, and understand the mapping between UI and API paths. These techniques enable robust integration and automation scenarios with any Dataverse data.
➤ Microsoft 365 APIs and Python Tutorial
➤ Python API integration tutorials
➤ What is an integration platform?
➤ Python Integration platform as a Service (iPaaS)
➤ What is an Enterprise Service Bus (ESB)? What is SOA?
➤ Open-source iPaaS in Python