Skip to content

Commit 6d80dd4

Browse files
Merge pull request #1 from microsoft/user/tpellissier/init
Initial proof-of-concept implementation
2 parents 8458805 + 9cbe56d commit 6d80dd4

13 files changed

Lines changed: 1566 additions & 425 deletions

File tree

.gitignore

Lines changed: 22 additions & 417 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 151 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,157 @@
1-
# Project
1+
# Dataverse SDK (Python) — Proof of Concept
22

3-
> This repo has been populated by an initial template to help get you started. Please
4-
> make sure to update the content to build a great experience for community-building.
3+
A minimal Python SDK to use Microsoft Dataverse as a database for Azure AI Foundry–style apps.
54

6-
As the maintainer of this project, please make a few updates:
5+
- Read (SQL) — Execute read-only T‑SQL via the McpExecuteSqlQuery Custom API. Returns `list[dict]`.
6+
- OData CRUD — Thin wrappers over Dataverse Web API (create/get/update/delete).
7+
- Metadata helpers — Create/inspect/delete simple custom tables (EntityDefinitions + Attributes).
8+
- Pandas helpers — Convenience DataFrame oriented wrappers for quick prototyping/notebooks.
9+
- Auth — Azure Identity (`TokenCredential`) injection.
710

8-
- Improving this README.MD file to provide a great experience
9-
- Updating SUPPORT.MD with content about this project's support experience
10-
- Understanding the security reporting process in SECURITY.MD
11-
- Remove this section from the README
11+
## Features
12+
13+
- Simple `DataverseClient` facade for CRUD, SQL (read-only), and table metadata.
14+
- SQL-over-API: T-SQL routed through Custom API endpoint (no ODBC / TDS driver required).
15+
- Table metadata ops: create simple custom tables with primitive columns (string/int/decimal/float/datetime/bool) and delete them.
16+
- Optional pandas integration (`PandasODataClient`) for DataFrame based create / get / query.
17+
18+
Auth:
19+
- Credential is optional; if omitted, the SDK uses `DefaultAzureCredential`.
20+
- You can pass any `azure.core.credentials.TokenCredential` you prefer; examples use `InteractiveBrowserCredential` for local runs.
21+
- Token scope used by the SDK: `https://<yourorg>.crm.dynamics.com/.default` (derived from `base_url`).
22+
23+
## Install
24+
25+
Create and activate a Python 3.13+ environment, then install dependencies:
26+
27+
```powershell
28+
# from the repo root
29+
python -m pip install -r requirements.txt
30+
```
31+
32+
Direct TDS via ODBC is not used; SQL reads are executed via the Custom API over OData.
33+
34+
## Configuration Notes
35+
36+
- For Web API (OData), tokens target your Dataverse org URL scope: https://yourorg.crm.dynamics.com/.default. The SDK requests this scope from the provided TokenCredential.
37+
- For complete functionalities, please use one of the PREPROD BAP environments, otherwise McpExecuteSqlQuery might not work.
38+
39+
### Configuration (DataverseConfig)
40+
41+
Pass a `DataverseConfig` or rely on sane defaults:
42+
43+
```python
44+
from dataverse_sdk import DataverseClient
45+
from dataverse_sdk.config import DataverseConfig
46+
47+
cfg = DataverseConfig() # defaults: language_code=1033, sql_api_name="McpExecuteSqlQuery"
48+
client = DataverseClient(base_url="https://yourorg.crm.dynamics.com", config=cfg)
49+
50+
# Optional HTTP tunables (timeouts/retries)
51+
# cfg.http_retries, cfg.http_backoff, cfg.http_timeout
52+
```
53+
54+
## Quickstart
55+
56+
Edit `examples/quickstart.py` and run:
57+
58+
```powershell
59+
python examples/quickstart.py
60+
```
61+
62+
The quickstart demonstrates:
63+
- Creating a simple custom table (metadata APIs)
64+
- Creating, reading, updating, and deleting records (OData)
65+
- Executing a read-only SQL query
66+
67+
## Examples
68+
69+
### DataverseClient (recommended)
70+
71+
Tip: You can omit the credential and the SDK will use `DefaultAzureCredential` automatically:
72+
73+
```python
74+
from dataverse_sdk import DataverseClient
75+
76+
base_url = "https://yourorg.crm.dynamics.com"
77+
client = DataverseClient(base_url=base_url) # uses DefaultAzureCredential by default
78+
```
79+
80+
```python
81+
from azure.identity import DefaultAzureCredential
82+
from dataverse_sdk import DataverseClient
83+
84+
base_url = "https://yourorg.crm.dynamics.com"
85+
client = DataverseClient(base_url=base_url, credential=DefaultAzureCredential())
86+
87+
# Create (returns created record)
88+
created = client.create("accounts", {"name": "Acme, Inc.", "telephone1": "555-0100"})
89+
account_id = created["accountid"]
90+
91+
# Read
92+
account = client.get("accounts", account_id)
93+
94+
# Update (returns updated record)
95+
updated = client.update("accounts", account_id, {"telephone1": "555-0199"})
96+
97+
# Delete
98+
client.delete("accounts", account_id)
99+
100+
# SQL (read-only) via Custom API
101+
rows = client.query_sql("SELECT TOP 3 accountid, name FROM account ORDER BY createdon DESC")
102+
for r in rows:
103+
print(r.get("accountid"), r.get("name"))
104+
```
105+
106+
### Custom table (metadata) example
107+
108+
```python
109+
# Create a simple custom table and a few primitive columns
110+
info = client.create_table(
111+
"SampleItem", # friendly name; defaults to SchemaName new_SampleItem
112+
{
113+
"code": "string",
114+
"count": "int",
115+
"amount": "decimal",
116+
"when": "datetime",
117+
"active": "bool",
118+
},
119+
)
120+
121+
entity_set = info["entity_set_name"] # e.g., "new_sampleitems"
122+
logical = info["entity_logical_name"] # e.g., "new_sampleitem"
123+
124+
# Create a record in the new table
125+
# Set your publisher prefix (used when creating the table). If you used the default, it's "new".
126+
prefix = "new"
127+
name_attr = f"{prefix}_name"
128+
id_attr = f"{logical}id"
129+
130+
rec = client.create(entity_set, {name_attr: "Sample A"})
131+
132+
# Clean up
133+
client.delete(entity_set, rec[id_attr]) # delete record
134+
client.delete_table("SampleItem") # delete the table
135+
```
136+
137+
Notes:
138+
- `create/update` return the full record using `Prefer: return=representation`.
139+
- For CRUD methods that take a record id, pass the GUID string (36-char hyphenated). Parentheses around the GUID are accepted but not required.
140+
- SQL is routed through the Custom API named in `DataverseConfig.sql_api_name` (default: `McpExecuteSqlQuery`).
141+
142+
143+
144+
### Pandas helpers
145+
146+
See `examples/quickstart_pandas.py` for a DataFrame workflow via `PandasODataClient`.
147+
148+
VS Code Tasks
149+
- Install deps: `Install deps (pip)`
150+
- Run example: `Run Quickstart (Dataverse SDK)`
151+
152+
## Limitations / Future Work
153+
- No batching, upsert, or association operations yet.
154+
- Minimal retry policy in library (network-error only); examples include additional backoff for transient Dataverse consistency.
12155

13156
## Contributing
14157

0 commit comments

Comments
 (0)