FinOps SDK Step 1: CRUD over OData /data/{EntitySet}#174
Open
rishi1212 wants to merge 4 commits into
Open
Conversation
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>
Contributor
There was a problem hiding this comment.
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
FinOpsClientplus arecordsoperations namespace implementingcreate/get/update/deleteagainst/data/{EntitySet}. - Adds a
TokenProvider(thread-safe token cache) andHttpClient(retries + error mapping) to back the client. - Adds a new
tests/finopsunit 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.
- 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 byAxODataControllerin the FinOps Platform.This is a new package alongside the existing
PowerPlatform.DataverseSDK. Nothing in the Dataverse package is touched.What's in the package
src/PowerPlatform/FinOps/client.pyFinOpsClient(env URL, scope<env>/.default, lifecycle,.recordsnamespace)_auth.pyTokenProvider— thread-safeAccessTokencache, 5-minute refresh skew_http.pyHttpClient— retries on 408/429/5xx withRetry-After+ jitter, 401-reauth, OData v4 headers, activity-id sniffingerrors.pyFinOpsError->AuthError/HttpError/NotFound/Concurrency/Throttledoperations/records.pyRecordOperations.create / get / update / delete+ composite-key formatter (OData literal escaping)Capability matrix delivered (Step 1)
POST /data/{EntitySet}client.records.create(entity_set, data)GET /data/{EntitySet}({key})client.records.get(entity_set, key, select=, expand=)PATCH /data/{EntitySet}({key})client.records.update(entity_set, key, changes, etag=)DELETE /data/{EntitySet}({key})client.records.delete(entity_set, key, etag=)Verification
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.LegalEntities(read originalName, PATCH to marker, GET back, PATCH restore)OData-EntityIdLocation parsing onCustomerGroupsFinOpsNotFoundErrormapping verifiedOut of scope (later steps)
Bulk insert/update, query-builder, batching, file streams, plug-in SDK, metadata write. Architectural blockers (no SQL endpoint, no
CreateMultiple/UpdateMultipleserver-side, no metadata-write API) remain valid.