Skip to content

FinOps SDK Step 1: CRUD over OData /data/{EntitySet}#174

Open
rishi1212 wants to merge 4 commits into
microsoft:mainfrom
rishi1212:user/riverma/finopsSDKStep1
Open

FinOps SDK Step 1: CRUD over OData /data/{EntitySet}#174
rishi1212 wants to merge 4 commits into
microsoft:mainfrom
rishi1212:user/riverma/finopsSDKStep1

Conversation

@rishi1212

Copy link
Copy Markdown

Summary

Adds a new PowerPlatform.FinOps sub-package implementing Step 1 of the FinOps Python SDK roadmap — the four basic CRUD verbs against the FinOps OData endpoint (/data/{EntitySet}), backed by AxODataController in the FinOps Platform.

This is a new package alongside the existing PowerPlatform.Dataverse SDK. Nothing in the Dataverse package is touched.

What's in the package

src/PowerPlatform/FinOps/

File Purpose
client.py FinOpsClient (env URL, scope <env>/.default, lifecycle, .records namespace)
_auth.py TokenProvider — thread-safe AccessToken cache, 5-minute refresh skew
_http.py HttpClient — retries on 408/429/5xx with Retry-After + jitter, 401-reauth, OData v4 headers, activity-id sniffing
errors.py FinOpsError -> AuthError / HttpError / NotFound / Concurrency / Throttled
operations/records.py RecordOperations.create / get / update / delete + composite-key formatter (OData literal escaping)

Capability matrix delivered (Step 1)

Verb HTTP SDK call
Create POST /data/{EntitySet} client.records.create(entity_set, data)
Retrieve GET /data/{EntitySet}({key}) client.records.get(entity_set, key, select=, expand=)
Update PATCH /data/{EntitySet}({key}) client.records.update(entity_set, key, changes, etag=)
Delete DELETE /data/{EntitySet}({key}) client.records.delete(entity_set, key, etag=)

Verification

  • Unit tests: pytest tests/finops -v -> 23/23 passing. Coverage includes URL/method/header/body shape for all four verbs, composite-key formatting (quote-escaping, empty-mapping rejection), error mapping (404 -> NotFound, 412 -> Concurrency, 500 -> HttpError), token caching, default scope.
  • Live FinOps env: Verified end-to-end against an internal Aurora env:
    • 4838 entity sets enumerated via service document
    • GET + PATCH round-trip on LegalEntities (read original Name, PATCH to marker, GET back, PATCH restore)
    • CREATE wire format + OData-EntityId Location parsing on CustomerGroups
    • DELETE wire format + FinOpsNotFoundError mapping verified

Out of scope (later steps)

Bulk insert/update, query-builder, batching, file streams, plug-in SDK, metadata write. Architectural blockers (no SQL endpoint, no CreateMultiple/UpdateMultiple server-side, no metadata-write API) remain valid.

Implements the four 'Yes' rows from the FinOps-SDK-Plan capability matrix:

- POST   /data/{EntitySet}            -> records.create

- GET    /data/{EntitySet}({key})     -> records.get

- PATCH  /data/{EntitySet}({key})     -> records.update

- DELETE /data/{EntitySet}({key})     -> records.delete

Package layout under src/PowerPlatform/FinOps:

- client.FinOpsClient (env URL, scope=<env>/.default, lifecycle, .records)

- _auth.TokenProvider (thread-safe AccessToken cache, 5-min refresh skew)

- _http.HttpClient (retries 408/429/5xx with Retry-After + jitter, 401 reauth, OData v4 headers)

- errors: FinOpsError -> AuthError | HttpError | NotFound | Concurrency | Throttled

- operations.records.RecordOperations (composite-key formatter, OData literal escaping)

Tests: 23 unit tests in tests/finops/ (all passing) covering URL/method/header/body

shape, composite-key formatting, error mapping (404/412/500), token caching.

Verified end-to-end against a live FinOps env (4838 entity sets enumerated; GET +

PATCH round-trip on LegalEntities; CREATE returns 201 + parsed Location header).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rishi1212 rishi1212 requested a review from a team as a code owner May 5, 2026 09:28
Copilot AI review requested due to automatic review settings May 5, 2026 09:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new PowerPlatform.FinOps subpackage implementing “Step 1” of a FinOps Python SDK: basic CRUD operations over the FinOps OData /data/{EntitySet} endpoint, with token caching, retrying HTTP transport, and structured error mapping.

Changes:

  • Introduces FinOpsClient plus a records operations namespace implementing create/get/update/delete against /data/{EntitySet}.
  • Adds a TokenProvider (thread-safe token cache) and HttpClient (retries + error mapping) to back the client.
  • Adds a new tests/finops unit test suite and a manual smoke-test script.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/PowerPlatform/FinOps/__init__.py Exposes the FinOps public surface and defines package metadata.
src/PowerPlatform/FinOps/client.py Implements FinOpsClient lifecycle/config and wires operation namespaces.
src/PowerPlatform/FinOps/_auth.py Adds token acquisition + refresh-skew caching via TokenProvider.
src/PowerPlatform/FinOps/_http.py Adds retrying HTTP transport and maps non-2xx responses to FinOps exceptions.
src/PowerPlatform/FinOps/errors.py Defines FinOps exception hierarchy (AuthError, HttpError, NotFound, etc.).
src/PowerPlatform/FinOps/operations/__init__.py Declares the operations namespace exports.
src/PowerPlatform/FinOps/operations/records.py Implements CRUD calls and OData key/literal formatting helpers.
tests/finops/__init__.py Marks the FinOps tests package.
tests/finops/test_records.py Validates CRUD URL/header/body shape, key formatting, error mapping, and token caching.
scratch_finops_smoketest.py Manual end-to-end harness for exercising CRUD against a real FinOps environment.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/finops/test_records.py
Comment thread src/PowerPlatform/FinOps/__init__.py
Comment thread src/PowerPlatform/FinOps/__init__.py
Comment thread src/PowerPlatform/FinOps/_http.py
Comment thread src/PowerPlatform/FinOps/operations/records.py
Comment thread src/PowerPlatform/FinOps/operations/records.py
rishi1212 and others added 3 commits May 14, 2026 09:41
- records.list(entity_set, *, filter, select, expand, orderby, top,
  page_size, cross_company): generator over @odata.nextLink with
  client-side top cap, Prefer: odata.maxpagesize header, and the
  FinOps-specific cross-company=true switch (verified live against
  Aurora aurorabapenvdc9e7 -- per-company default returns 0 rows
  for CustomerGroups, cross_company=True paginates correctly).

- New operations.metadata namespace (read-only):
    list_data_entities / get_data_entity
    list_public_entities / get_public_entity
    list_public_enumerations / get_public_enumeration
  URLs live at /metadata/* (NOT under /data/). Metadata controllers
  reject \ with HTTP 400, so the SDK intentionally does not
  expose select on these methods. \ is sent server-side but
  enforced client-side because the controllers currently ignore it.

- 20 new unit tests (test_records_list.py: 11, test_metadata.py: 9)
  covering pagination, query-option emission, top short-circuit,
  cross-company flag, OData single-quote escaping, empty-name
  validation, and nextLink follow-through. Full pytest sweep:
  1369 passed, 0 failed.

- Live verified end-to-end against
  https://aurorabapenvdc9e7.operations.int.dynamics.com (legal entity
  'dat'): all six smoketest phases green including typed
  FinOpsNotFoundError mapping for missing metadata names.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
FinOps SDK Step 2: paginated list iterator + metadata reads
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants