Quantum: Add 'az quantum suite-offer quotas' command - #10285
Quantum: Add 'az quantum suite-offer quotas' command#10285v-elegacheva wants to merge 14 commits into
Conversation
List the Quantum suite offers available to the subscription (provider, location, and subscription-level quota allocations) via the control-plane SuiteOffers API. Bumps the extension to 1.0.0b24.
…fer-list # Conflicts: # src/quantum/HISTORY.rst # src/quantum/setup.py
Adds 'az quantum suite-offer quotas --provider-id' which returns v2 quota allocations merged with their consumed usages for a suite offer provider account. Combines the control-plane suite offer allocations with the data-plane (-v2 endpoint) quota usages, reporting allocated/used/remaining standard and high priority minutes per subscription and target scope.
|
Hi v-elegacheva, |
|
Thank you for your contribution v-elegacheva! We will review the pull request and get back to you soon. |
| std_used = usage_values.standard_minutes_lifetime if usage_values is not None else None | ||
| high_used = usage_values.high_minutes_lifetime if usage_values is not None else None | ||
|
|
||
| row = OrderedDict() |
There was a problem hiding this comment.
Did you consider creating a structure for return type to improve readability? and probably we can skip remaining and stick to initial structure like this:
{
"providerId" : "atom-dev",
"scope" : "SubscriptionTarget",
"targetId" : "msft.sim.ac1000.physical",
"allocation" : {
"standardMinutesLifetime" : 600,
"highMinutesLifetime" : 60
},
"usage" : {
"standardMinutesLifetime" : 120,
"highMinutesLifetime" : 12
}
}
There was a problem hiding this comment.
I restructured to match your proposed shape. Each row is now:
{
"providerId": "...",
"scope": "SubcriptionTarget",
"targetId": "...",
"allocation": { "standardMinutesLifetime": 0, "highMinutesLifetime": 0 },
"usage": {"standardMinutesLifetime": 0, "highMinutesLifetime": 0}
}
remaining and lastModifiedTime are dropped. I added a small _minutes() helper to build the nested blocks. The table transformer and help text were also updated to match
There was a problem hiding this comment.
thanks, but it still dict, do you think there will be benefit of creating a type with all of these fields and have dot access to the fields?
There was a problem hiding this comment.
The return value of a CLI custom command is serialized straight to the user-facing output, and the CLI's todict serializer uses each object's raw attribute names (vars()). So a plain class/dataclass would emit snake_case keys (provider_id, standard_minutes_lifetime) instead of the providerId / standardMinutesLifetime contract, and a namedTuple serializes as a JSON array. OrderedDict gives exact control over the camelCase keys and ordering that define this command's output, and it is consistent with the rest of the quantum extension (all handlers/ transformers return dicts or SDK models). The dot-access benefit would only apply inside this ~ 15 line builder, which _minutes() already simplifies. If you'd like the shape documented in code, i can switch the row to a TypedDict. That gives type-checking + editor hints and still serializes correctly as a dict. A full dataclass would need custom camelCase serialization to avoid changing the output. Which way would you prefer?
…age output Per review: build one row per targetQuota (SubscriptionTarget scope only), restructure each row into nested 'allocation' and 'usage' blocks, and drop the computed 'remaining' field.
Non-functional follow-ups from code review: add the canary branch to base_url_v2 for parity with base_url, add a @live_only scenario test for 'suite-offer quotas', correct the 'suite-offer list' help summary, and comment the unused factory args.
|
Quantum |
| } | ||
|
|
||
| rows = [] | ||
| for target_quota in sorted(offer.properties.target_quotas or [], key=lambda q: q.target_id or ""): |
There was a problem hiding this comment.
what is the reason of sorting target quotas here?
There was a problem hiding this comment.
It is just to give deterministic, stable output ordering. The service does not guarantee an order for targetQuotas (or the usages list), so sorting by targetId keeps the JSON/ table rows consistent across runs. Which also keeps diffs and the live test stable. I can drop it if you'd rather preserve the service's order :)
|
|
||
| row = OrderedDict() | ||
| row["providerId"] = provider_id | ||
| row["scope"] = "SubscriptionTarget" |
There was a problem hiding this comment.
we can reuse usage.Scope here instead of magic string
There was a problem hiding this comment.
I agree! I did find something worth attention however: a target row can have no matching usage (usage is None for targets with no consumption), so usage.scope isn't always available to read. Since every target row is SubscriptionTarget - scoped by definition, I'll lift the literal into a named constant so it is not a magic string and stays independent of whether a usage row exists. If you would prefer, I can instead read usage.scope when present and fall back to the constant. Please let me know what you would prefer!
| row["providerId"] = provider_id | ||
| row["scope"] = "SubscriptionTarget" | ||
| row["targetId"] = target_quota.target_id | ||
| row["allocation"] = _minutes( |
There was a problem hiding this comment.
I think in UI we show allocation and usage in hours? align with it
There was a problem hiding this comment.
The underlying ARM / DP fields are standardMinutesLifetime / highMinutesLifetime. The values are minutes by contract and the key names literally say "Minutes". The CLI mirrors the service payload, so converting to hours would make the value diagree with its own filed name and with ARM. My instinct was keeping the JSON in minutes (true to contract) and if it helps parity with the UI, adding hours to the table view only. If you would prefer to fully match the UI, we would need new hour-names fields (like standardHoursLifetime) rather than silently dividing the existing ones. Please let me know how you'd like to proceed on this one :)
Lists targets and their status for a suite offer provider account via the data plane, without requiring a workspace. Fixes single-object ProviderStatus parsing, consolidates the data-plane suite-offer client factory, and bumps the extension to 1.0.0b27.
Address review: the data-plane getProviderStatus endpoint returns a single ProviderStatus object per the spec, not a list. Rename the vendored list_provider_status to get_provider_status (sync + async) returning a single ProviderStatus, drop the wrap-in-list workaround, and have the target-list handler wrap the result for the shared table transformer. Update tests accordingly.
…ota allocations merged with usages Replaces the legacy data-plane quotas listing with v2 workspace target quota allocations (from ARM) merged with their consumed quota usages from the data-plane v2 quotaUsages endpoint. The workspace quotaUsages endpoint requires a providerId query parameter, so usages are fetched per provider. Adds a table transformer and unit tests.
🤖 PR Validation —⚠️ Review suggested
Summary
Adds two read-only data-plane (v2) commands to the
quantumextension for suite offerprovider accounts, neither of which requires an Azure Quantum workspace:
az quantum suite-offer quotas --provider-id <id>— lists the v2 quota allocations(limits) for each target of a provider account, merged with their consumed usages
(standard and high priority minutes over the account lifetime).
az quantum suite-offer target list --provider-id <id>— lists the targets exposedby a provider account together with their current availability and average queue time.
Both commands resolve the provider account's region from the control-plane suite offer,
then call the corresponding v2 data-plane endpoint directly.
Changes
ProviderStatus parsing fix: the
providerStatusendpoint returns a singleProviderStatusobject (not a paged{ "value": [...] }envelope or bare array), solist_provider_statusnow wraps the single object in a list. A paged envelope / barearray is still tolerated for forward-compatibility. A regression unit test covers this.
into a single
cf_suite_offers_data_plane, matching the file's "one factory per.services.Xaccessor" convention.TargetStatusmodel declaresnumQubits/targetProfile/metadata(surfaced asnull), while the DataPlaneV2backend additionally emits
averageQueueTimeHighPriority/averageQueueTimeStandardPrioritythat the model doesn't declare. The version-tolerant model passes unknown wire fields
through, so output is faithful and the table transformer (which uses only
averageQueueTime) is unaffected. Aligning the vendored model can be handled separately.Testing
azext_quantum/tests/latest/test_quantum_suite_offers.py(build-request,deserialization, table-transform, single-object wrap, and merge-logic tests).
providers; valid non-empty results for providers with no targets); table and JSON output
confirmed.
flake8/pylintclean.