You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 142b3ac
Browse filesBrowse the repository at this point in the historyBrowse files
# SDK-native column constraints — Layer 1 of reducing raw-`urllib` usage
## The problem this fixes
In the Dataverse plugin's telemetry, a large share of traffic is **raw
`urllib`** (hand-rolled HTTP) instead of the managed SDK. The single
biggest driver is **schema metadata** on the `dv-metadata` skill: agents
were forced to hand-build calls to `EntityDefinitions/Attributes`
because the SDK **could not express column constraints**. Concretely,
three operations had no SDK path:
1. **Create** a column with constraints (`MaxLength`,
`MinValue`/`MaxValue`, `Format`, `RequiredLevel`, a custom
`DisplayName`).
2. **Update** an existing column's constraints (requires the
counter-intuitive `PUT`-not-`PATCH` plus a derived `@odata.type`
discriminator).
3. **Read** type-specific fields back (needs a second request with an
`@odata.type` URL cast).
So for a routine ask like *"add a Rating 1–5 and a 2000-char Comment"*,
the agent dropped to raw `urllib`, hit the metadata quirks, and looped.
This PR gives all three an SDK path, so agents stay on the managed
client (which carries auth, retries, and telemetry attribution) and
**never hand-roll the metadata Web API**.
> **Scope: this is Layer 1 (SDK / product fixes).** Layer 2 — **fixing
the skills** (updating `dv-metadata` to teach the new SDK spec and stop
describing the raw Web-API workaround) — is a separate follow-up in the
plugin repo and depends on this PR shipping.
## What's in this PR
Four changes, all backward-compatible, sync + async parity.
### 1. Create with constraints (closes#194)
`tables.create` / `add_columns` now accept a **dict** column spec
alongside the existing `str` / `Enum`:
```python
client.tables.create("cfb_CustomerFeedback", {
"cfb_Name": "string", # unchanged
"cfb_Rating": {"type": "int", "min_value": 1, "max_value": 5},
"cfb_Comment": {"type": "memo", "max_length": 2000, "display_name": "Comment"},
})
```
Override keys: `max_length`, `min_value`, `max_value`, `precision`,
`format`, `required`, `display_name`.
### 2. Update an existing column (closes#202)
New `tables.update_column` / `update_columns`. The SDK reads the typed
attribute, applies the overrides, and issues the `PUT` with the right
`@odata.type` internally — the caller never sees a verb or a
discriminator:
```python
client.tables.update_column("cfb_CustomerFeedback", "cfb_Comment",
{"max_length": 4000, "display_name": "Customer Comment"})
```
### 3. Typed reads in one request (closes#203)
`typed=True` on `list_columns` / `get_column`, backed by
`RetrieveMetadataChanges` with property projection — type-specific
fields come back in a **single** request, no `@odata.type` URL cast:
```python
col = client.tables.get_column("account", "emailaddress1", typed=True) # col["MaxLength"], etc.
cols = client.tables.list_columns("account", typed=True)
```
### 4. Attribution allowlist
Add `dv-overview` + `erp-xpp` to `OperationContext._ALLOWED_SKILLS`
(parity with the plugin's `scripts/auth.py`, which allows all 10).
Previously `get_client('dv-overview'|'erp-xpp')` raised `ValueError` on
the SDK side, and the plugin silently fell back to raw `urllib` for
those two skills.
### Compatibility
`str` / `Enum` column specs and every existing signature are unchanged;
the only new parameter is a keyword-only `typed=False` on
`list_columns`. No CHANGELOG / version edit (per CONTRIBUTING —
maintainers handle those at release).
## Testing
**Unit** — full suite green: **2437 passed** (sync + async). New
coverage: dict-spec overrides, `update_column` (PUT verb + `@odata.type`
+ override application + error paths), `RetrieveMetadataChanges` typed
reads (request shape, projection, criteria), `get_column(typed=True)`,
and allowlist parity.
**Live end-to-end** against a Dataverse org (`*.crm10.dynamics.com`) —
**12/12 checks pass**:
| Check | Result |
|---|---|
| #194 create table w/ bounded int (1–5) + 2000-char renamed memo | PASS
|
| #203 typed read → `MinValue=1`, `MaxValue=5`, `MaxLength=2000` (one
request) | PASS |
| #203 `list_columns(typed=True)` returns typed fields | PASS |
| #202 `update_column` memo `2000 → 4000`, re-read confirms | PASS |
| core CRUD (create / retrieve / update / list / delete) — no regression
| PASS |
For contrast, the **unmodified** SDK raises `ValueError: Unsupported
column spec type ... <class 'dict'>` on the very same create — the exact
gap this PR closes.
Copy file name to clipboardExpand all lines: README.md
+25-1Lines changed: 25 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -111,7 +111,7 @@ df = results.to_dataframe() # same rows as a pandas DataFrame
111
111
112
112
### Define and evolve schema
113
113
114
-
`client.tables` creates and inspects tables, columns, relationships, and alternate keys. Column types are simple strings (`"string"`, `"int"`, `"decimal"`, `"money"`, `"datetime"`, `"bool"`, `"memo"`); pass an `IntEnum` subclass to create a choice column. `tables.get` returns `None` when the table does not exist, which makes schema setup idempotent. Every table gets a primary name column automatically — `<prefix>_Name` unless you pass `primary_column` — so do not list it in `columns`.
114
+
`client.tables` creates and inspects tables, columns, relationships, and alternate keys. Column types are simple strings (`"string"`, `"int"`, `"decimal"`, `"money"`, `"datetime"`, `"bool"`, `"memo"`); pass an `IntEnum` subclass to create a choice column, or a dict spec (e.g. `{"type": "memo", "max_length": 2000}`) to set constraints like length, range, format, required level, or display name. `tables.get` returns `None` when the table does not exist, which makes schema setup idempotent. Every table gets a primary name column automatically — `<prefix>_Name` unless you pass `primary_column` — so do not list it in `columns`.
Relationship methods take logical names, which are always the schema name in lowercase. For choice columns, many-to-many relationships, and alternate keys, see [Customize tables and columns](https://learn.microsoft.com/power-apps/developer/data-platform/sdk-python/metadata) and [Manage table relationships](https://learn.microsoft.com/power-apps/developer/data-platform/sdk-python/relationships).
0 commit comments