Update Communication Identity API version to 2026-09-23 - #48973
Open
John Fraser (JohnRFraser) wants to merge 16 commits into
Open
Update Communication Identity API version to 2026-09-23#48973John Fraser (JohnRFraser) wants to merge 16 commits into
John Fraser (JohnRFraser) wants to merge 16 commits into
Conversation
…onfig.yaml', API Version: 2026-09-23, SDK Release Type: stable, and CommitSHA: '1893171028aed5a757fc78c2d4aa439c14df4afc' in SpecRepo: 'https://github.com/Azure/azure-rest-api-specs' Pipeline run: https://dev.azure.com/azure-sdk/internal/_build/results?buildId=6802367 Refer to https://eng.ms/docs/products/azure-developer-experience/develop/sdk-release/sdk-release-prerequisites to prepare for SDK release.
Adapt the hand-written sync and async clients to the DPG-generated service layer while keeping the public API surface unchanged. Generated-shape changes absorbed here: - Client class renamed CommunicationIdentityClient -> IdentityClient. - One operation group became three (identity_operations, teams_user_operations, teams_extension_operations), matching the three TypeSpec interfaces. - Enum renamed CommunicationTokenScope -> CommunicationIdentityTokenScope; aliased back so the public name is unchanged. Two behaviors deliberately preserved against generator changes: 1. AccessToken.expires_on stays the raw service string. TypeSpec declares expiresOn as utcDateTime, so attribute access now deserializes to datetime. Reading the key instead returns the untouched wire value. Note .isoformat() is not equivalent: it changes the format and truncates 7-digit fractional seconds to 6. 2. expiresInMinutes is omitted when not supplied. msrest dropped None fields during serialization; the generated code forwards the request body as-is, which would send an explicit null for a property the service constrains to [60, 1440]. Default API version moves 2023-10-01 -> 2025-06-30. Verified: 97 offline unit tests pass, identical to the pre-migration baseline. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add V2026_09_23 to the ApiVersion enum and make it the default. Bump the package to 2.0.0, a coordinated major increment across all four language SDKs rather than a semver-driven one: the public API surface is unchanged and the 97 offline unit tests pass exactly as before. Re-verified after regeneration: - expiresInMinutes is still omitted when not supplied and present when it is, sync and async. This is the build_token_request_body fix; msrest used to drop None fields, and without it an explicit null reaches a property the service constrains to [60, 1440]. - AccessToken.expires_on is still the raw service string, byte-identical. - Every public attribute on every returned object reads back populated, sync and async, including the identifier properties and kind. - api-version=2026-09-23 confirmed on the wire. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The AutoRest configuration that recorded this constraint was deleted with swagger/SWAGGER.md, so to_access_token is now the only place the reasoning survives. Record it there: that returning a datetime would break callers, that this already happened once under AutoRest, and that .isoformat() is not an adequate substitute because it reformats and truncates fractional seconds. Comment only - no behavior change. 97 tests pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Every wire check performed during this migration was an ad hoc probe. None was committed, so nothing in CI asserted the three behaviours the DPG move silently changed. Grepping tests/ for api-version returned nothing. Adds 9 tests covering, at the transport layer: - expiresInMinutes omitted when no expiry is requested, present when one is - AccessToken.expires_on carrying the raw service string, not a datetime - the targeted api-version reaching the wire on all six operations Each assertion is mutation-tested. Reverting build_token_request_body fails 2, reverting to_access_token to attribute access fails 3, and reverting DEFAULT_VERSION fails 2. That last figure was 1 before this commit. Mutation testing showed the per-operation check compared the wire against DEFAULT_VERSION itself, which is self-referential and cannot detect a wrong default. It now asserts a literal. 106 offline tests pass (97 existing plus these 9). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The TypeSpec emitter drops three request headers the AutoRest client sent:
Content-Type on the bodyless create_user POST, and Accept on revoke_tokens
and delete_user. The service is indifferent to all three - measured against a
live resource - but a proxy, gateway or request log keying on headers would
observe the change, so under the black-box-identical requirement they are
restored.
Accept is restored by passing headers= through to the generated operation.
Content-Type could not be. The generated create operation nulls content_type
whenever no body is present, twice:
content_type = content_type if body else None
content_type = content_type or "application/json" if body else None
so no argument reaches the request builder. It is restored by a SansIOHTTPPolicy
matched narrowly to a bodyless POST to the identities collection, leaving the
body-bearing create_user_and_token untouched.
That same discard also drops a caller-supplied Content-Type, which the AutoRest
client honoured - a second regression found while testing the first. create_user
now moves any caller value into the request context for the policy to reapply.
Verified: all six operations compared against the pre-migration client, sync and
async, for accept and content-type - zero mismatches. create_user still sends no
body. 15 wire tests (6 new), each mutation-tested: removing the policy fails 2,
neutering the Accept merge fails 2. 112 offline tests pass.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
_shared/ (13 files) and aio/__init__.py were removed by the generation pipeline's unscoped rmtree of the package namespace directory. They are unmodified by this migration, so no convenience-layer commit carries them; they are restored verbatim from the merge-base. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
client.tsp renames the token scope union for Python, so the generated enum is CommunicationTokenScope directly and the compensating alias import now refers to a name that no longer exists. Hand-written change only; the generated layer is left exactly as the pipeline emitted it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The pipeline-generated pyproject.toml classifies the distribution as
"Development Status :: 4 - Beta" while the package ships as 2.0.0, a stable
GA release. The repo's own gate rejects that combination:
ci_tools.functions.verify_package_classifiers(
"azure-communication-identity", "2.0.0",
["Development Status :: 4 - Beta"])
-> False, "has version 2.0.0 and is a GA release, but had development
status 'Development Status :: 4 - Beta'. Expecting a development
classifier that is equal or greater than
'Development Status :: 5 - Production/Stable'."
setup.py does already carry "Development Status :: 5 - Production/Stable",
but that has no effect here and is not a mitigation. pyproject.toml declares a
[project] table, so under PEP 621 it is authoritative for package metadata and
setup.py's classifiers are ignored entirely. Building the sdist before this
change produced:
Version: 2.0.0
Classifier: Development Status :: 4 - Beta
and after it produces "Development Status :: 5 - Production/Stable". The gate
function returns True for the corrected value.
pyproject.toml is emitted by the generation pipeline, so this recurs on every
regeneration until the emitter or its configuration is corrected upstream.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The recorded sessions were captured against api-version 2023-10-01. This
package now defaults to 2026-09-23, so every recorded request failed to match
its cassette:
ResourceNotFoundError: Playback failure
Unable to find a record for the request ... Uri doesn't match
Measured, not assumed: the assets tag
python/communication/azure-communication-identity_b8abdd90a9 was cloned and its
92 cassettes scanned. 2023-10-01 is the only api-version present, in 71
occurrences.
Adds a URI regex sanitizer mapping the current version back to the recorded
one. The two versions are functionally identical for these operations, so the
recordings remain valid and only the query string differs. This retires itself:
once the cassettes are re-recorded at 2026-09-23 the regex matches nothing.
ignored_query_parameters was deliberately not used. It substitutes a "Volatile"
literal on both sides, so any api-version would match any recording - a blanket
hole in the single field this release changes. The regex maps one exact version
to one other exact version, so every other value still fails to match.
Known limitation: sanitizers apply to both the recording and the request, so the
two versions become mutually interchangeable and a regression back to 2023-10-01
would not be caught here. tests/test_wire_contract.py asserts the exact
api-version against a literal, which covers that gap.
Verified by playback against the real cassettes, not by inspection:
with the sanitizer 49 passed
bogus target version (no match) 34 failed
bogus replacement (rewrites to nothing) 34 failed
sanitizer removed entirely 34 failed
restored 49 passed
The 15 that pass in every case are client-side validation and Teams-user tests
that raise before any HTTP request is issued, so no cassette is consulted.
Full suite: 212 passed, matching the 163 passed / 49 failed reported by CI at
build 6811270.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Completes the msrest-parity fix started for expiresInMinutes. The old layer dropped None fields during serialization; the generated code forwards the body as-is. build_token_request_body already omitted expiresInMinutes when unset but still set scopes unconditionally, so a None scope list reached the wire as an explicit null. Measured against the shipped package at 7b1196e: OLD (AutoRest) NEW (before fix) get_token(user, None) {} {"scopes": null} create_user_and_token(None) {} {"createTokenWithScopes": null} get_token(user, []) {"scopes": []} {"scopes": []} create_user_and_token([]) {"createTokenWithScopes": []} {"createTokenWithScopes": []} After the fix all four match the old client exactly. An empty list is a value rather than an absence and is still sent. This restores prior behaviour and deliberately adds no validation. Checked across eight null-argument cases that exception behaviour is unchanged: the AutoRest client did not raise on a None scope list, and neither does this. The request is still issued and the service still rejects it. Found while checking whether the Python emitter dropped argument null guards the way the C# emitter did. It did not - Python parity is 8/8 on exceptions and was never guarded in the first place - but the check surfaced this body-level difference, which no request-comparison test would have caught because the existing tests only covered the expiry field. Adds three regression tests. Reverting the fix fails two of them. 215 offline tests pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Regression introduced by the previous commit. Omitting scopes when None made
create_user_and_token(None) serialize to {}, and the two guards that should have
covered that case disagree about what "empty" means:
generated create(): content_type or "application/json" if body else None
body is the dict {} -> falsy -> content_type dropped
-> azure-core falls back to text/plain
BodylessCreateContentTypePolicy: if http_request.body: return
body is the string "{}" -> truthy -> policy skipped
So an empty JSON body fell between them. Before the scopes fix the body was
{"createTokenWithScopes": null}, which is non-empty, so this never surfaced.
The policy now keys on whether a JSON content type is already set rather than on
body presence, which is the condition it actually cares about.
Measured against the PUBLISHED 1.5.0 package from PyPI - not repo main, whose
1.6.0 was never released - across seven body and header cases:
create_user_and_token(None) published: CT=application/json
before: CT=text/plain
after: CT=application/json
All 7 cases now match published 1.5.0 exactly for body, Content-Type and Accept.
Adds a regression test. Reverting the policy condition fails it. 216 offline
tests pass.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Gateways, front doors, WAFs and throttling pages return HTML or non-envelope
JSON on failure, so this path is reached through infrastructure rather than
caller error. Customer code branching on e.status_code - retrying on 429, for
example - depends on the status surviving a body the deserializer cannot parse.
Python is already correct. The generated code raises
HttpResponseError(response=response, model=error)
taking the status from the response rather than from the parsed model, and
_failsafe_deserialize swallows the parse failure and returns None. The
equivalent JavaScript client raises a bare TypeError with no status in the same
cases, which is the existence proof that this can regress.
These tests pin the black-box guarantee rather than the mechanism: they assert
the exception type and status code only, not the internals of
_failsafe_deserialize.
Measured against the shipped package at 7b1196e - identical in all five
cases, and exception types match across nine statuses including the specialised
subclasses (ClientAuthenticationError for 401, ResourceNotFoundError for 404,
ResourceExistsError for 409).
Mutation: making _failsafe_deserialize propagate instead of swallowing fails
three of the four malformed-body cases. 221 offline tests pass.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
John Fraser (JohnRFraser)
requested review from
a team,
Aigerim (AikoBB) and
Maqsood Jafferi (mjafferi-msft)
as code owners
September 11, 2026 07:03
John Fraser (JohnRFraser)
requested review from
Joel Eniqs (Joeleniqs),
alexandra142 and
paveldostalms
and
a balanced review from Copilot
September 11, 2026 07:03
Copilot started reviewing on behalf of
John Fraser (JohnRFraser)
September 11, 2026 07:03
View session
|
Azure Pipelines: Successfully started running 1 pipeline(s). 10 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Add the CHANGELOG entry and focused API-version tests.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Updates the Communication Identity API version enum, default, and sync/async documentation to 2026-09-23.
Changes:
- Adds
ApiVersion.V2026_09_23as the default. - Aligns sync and async client documentation.
File summaries
| File | Changes and findings |
|---|---|
sdk/communication/azure-communication-identity/azure/communication/identity/aio/_communication_identity_client_async.py |
Updates async client documentation. |
sdk/communication/azure-communication-identity/azure/communication/identity/_communication_identity_client.py |
Updates sync client documentation. |
sdk/communication/azure-communication-identity/azure/communication/identity/_api_versions.py |
Adds and selects the new API version. Nit (3 votes): Add an unreleased CHANGELOG entry. Moderate (2 votes): Add focused sync/async coverage for the default and override path. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
|
|
||
|
|
||
| DEFAULT_VERSION = ApiVersion.V2023_10_01 | ||
| DEFAULT_VERSION = ApiVersion.V2026_09_23 |
|
|
||
|
|
||
| DEFAULT_VERSION = ApiVersion.V2023_10_01 | ||
| DEFAULT_VERSION = ApiVersion.V2026_09_23 |
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.
Description
Update the Communication Identity client API-version enum and default to
2026-09-23. The sync and async client documentation now reports the same default.This is an API-version-only change and does not regenerate the client.
All SDK Contribution checklist:
General Guidelines and Best Practices
Testing Guidelines
Validation: 117 code-only tests passed. Playback tests could not run because this checkout does not contain the required recordings.