Skip to content

Commit 8510b25

Browse files
committed
Merge branch 'main' of https://github.com/Azure/azure-sdk-for-python into pipeline-batch-run-mode
2 parents 3937658 + fe880b7 commit 8510b25

102 files changed

Lines changed: 10018 additions & 2363 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.vscode/cspell.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"sdk/digitaltwins/azure-digitaltwins-core/**",
6969
"sdk/evaluation/azure-ai-evaluation/azure/ai/evaluation/_vendor/**",
7070
"sdk/evaluation/azure-ai-evaluation/tests/**",
71+
"sdk/evaluation/azure-ai-evaluation/samples/agent_evaluators/**",
7172
"sdk/eventhub/azure-eventhub-checkpointstoretable/**",
7273
"sdk/eventhub/azure-eventhub-checkpointstoreblob-aio/**",
7374
"sdk/eventhub/azure-eventhub/**",
@@ -403,6 +404,7 @@
403404
"rtsp",
404405
"rtype",
405406
"rwdlacu",
407+
"sansiohttppolicy",
406408
"scbedd",
407409
"sdist",
408410
"sdpath",
@@ -1400,7 +1402,10 @@
14001402
"upia",
14011403
"xpia",
14021404
"expirable",
1403-
"ralphe"
1405+
"ralphe",
1406+
"Inadherent",
1407+
"nbformat",
1408+
"nbconvert",
14041409
]
14051410
},
14061411
{

eng/apiview_reqs.txt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1-
apiview-stub-generator==0.3.16
2-
azure-pylint-guidelines-checker==0.0.7
3-
pylint<3.0.0
1+
astroid==2.15.8
2+
charset-normalizer==3.4.1
3+
dill==0.3.9
4+
isodate==0.6.1
5+
isort==5.13.2
6+
lazy-object-proxy==1.10.0
7+
mccabe==0.7.0
8+
pkginfo==1.12.1.2
9+
platformdirs==4.3.6
10+
pylint==2.17.7
11+
pylint-guidelines-checker==0.0.7
12+
six==1.17.0
13+
tomli==2.2.1
14+
tomlkit==0.13.2
15+
typing_extensions==4.12.2
16+
wrapt==1.17.2
17+
apiview-stub-generator==0.3.17

eng/common/TestResources/New-TestResources.ps1

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ param (
9393
})]
9494
[array] $AllowIpRanges = @(),
9595

96+
# Instead of running the post script, create a wrapped file to run it with parameters
97+
# so that CI can run it in a subsequent step with a refreshed azure login
98+
[Parameter()]
99+
[string] $SelfContainedPostScript,
100+
96101
[Parameter()]
97102
[switch] $CI = ($null -ne $env:SYSTEM_TEAMPROJECTID),
98103

@@ -625,9 +630,41 @@ try {
625630
SetResourceNetworkAccessRules -ResourceGroupName $ResourceGroupName -AllowIpRanges $AllowIpRanges -CI:$CI
626631

627632
$postDeploymentScript = $templateFile.originalFilePath | Split-Path | Join-Path -ChildPath "$ResourceType-resources-post.ps1"
633+
634+
if ($SelfContainedPostScript -and !(Test-Path $postDeploymentScript)) {
635+
throw "-SelfContainedPostScript is not supported if there is no 'test-resources-post.ps1' script in the deployment template directory"
636+
}
637+
628638
if (Test-Path $postDeploymentScript) {
629-
Log "Invoking post-deployment script '$postDeploymentScript'"
630-
&$postDeploymentScript -ResourceGroupName $ResourceGroupName -DeploymentOutputs $deploymentOutputs @PSBoundParameters
639+
if ($SelfContainedPostScript) {
640+
Log "Creating invokable post-deployment script '$SelfContainedPostScript' from '$postDeploymentScript'"
641+
642+
$deserialized = @{}
643+
foreach ($parameter in $PSBoundParameters.GetEnumerator()) {
644+
if ($parameter.Value -is [System.Management.Automation.SwitchParameter]) {
645+
$deserialized[$parameter.Key] = $parameter.Value.ToBool()
646+
} else {
647+
$deserialized[$parameter.Key] = $parameter.Value
648+
}
649+
}
650+
$deserialized['ResourceGroupName'] = $ResourceGroupName
651+
$deserialized['DeploymentOutputs'] = $deploymentOutputs
652+
$serialized = $deserialized | ConvertTo-Json
653+
654+
$outScript = @"
655+
`$parameters = `@'
656+
$serialized
657+
'`@ | ConvertFrom-Json -AsHashtable
658+
# Set global variables that aren't always passed as parameters
659+
`$ResourceGroupName = `$parameters.ResourceGroupName
660+
`$DeploymentOutputs = `$parameters.DeploymentOutputs
661+
$postDeploymentScript `@parameters
662+
"@
663+
$outScript | Out-File $SelfContainedPostScript
664+
} else {
665+
Log "Invoking post-deployment script '$postDeploymentScript'"
666+
&$postDeploymentScript -ResourceGroupName $ResourceGroupName -DeploymentOutputs $deploymentOutputs @PSBoundParameters
667+
}
631668
}
632669

633670
if ($templateFile.jsonFilePath.EndsWith('.compiled.json')) {

eng/common/TestResources/deploy-test-resources.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ parameters:
99
ResourceType: test
1010
UseFederatedAuth: true
1111
PersistOidcToken: false
12+
SelfContainedPostScript: self-contained-test-resources-post.ps1
1213

1314
# SubscriptionConfiguration will be splatted into the parameters of the test
1415
# resources script. It should be JSON in the form:
@@ -89,6 +90,8 @@ steps:
8990
# Write the new SubscriptionConfiguration to be used by the remove test resources
9091
Write-Host "##vso[task.setvariable variable=SubscriptionConfiguration;]$($subscriptionConfiguration | ConvertTo-Json -Compress)"
9192
93+
$postScriptPath = $${{ parameters.PersistOidcToken }} ? '$(Agent.TempDirectory)/${{ parameters.SelfContainedPostScript }}' : $null
94+
9295
# The subscriptionConfiguration may have ArmTemplateParameters defined, so
9396
# pass those in via the ArmTemplateParameters flag, and handle any
9497
# additional parameters from the pipelines via AdditionalParameters
@@ -100,10 +103,31 @@ steps:
100103
@subscriptionConfiguration `
101104
-AdditionalParameters ${{ parameters.ArmTemplateParameters }} `
102105
-AllowIpRanges ('$(azsdk-corp-net-ip-ranges)' -split ',') `
106+
-SelfContainedPostScript $postScriptPath `
103107
-CI `
104108
-Force `
105109
-Verbose | Out-Null
106110
111+
- ${{ if eq(parameters.PersistOidcToken, true) }}:
112+
# ARM deployments that take longer than 10-15 minutes (e.g. HSM) can
113+
# cause post scripts to fail with expired credentials.
114+
# Add a new task with a refreshed token as a workaround to this issue.
115+
- task: AzureCLI@2
116+
displayName: Test Resources Post with refreshed login
117+
env:
118+
${{ insert }}: ${{ parameters.EnvVars }}
119+
inputs:
120+
azureSubscription: ${{ parameters.ServiceConnection }}
121+
addSpnToEnvironment: true
122+
scriptLocation: inlineScript
123+
scriptType: pscore
124+
inlineScript: |
125+
$env:ARM_OIDC_TOKEN = $env:idToken
126+
$scriptPath = '$(Agent.TempDirectory)/${{ parameters.SelfContainedPostScript }}'
127+
Write-Host "Executing self contained test resources post script '$scriptPath'"
128+
& $scriptPath
129+
Remove-Item $scriptPath # avoid any possible complications when we run multiple deploy templates
130+
107131
- ${{ else }}:
108132
- pwsh: |
109133
eng/common/scripts/Import-AzModules.ps1

eng/common/scripts/job-matrix/Create-PrJobMatrix.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ function GeneratePRMatrixForBatch {
144144
}
145145
}
146146

147-
if ($matrixConfig.PSObject.Properties['PRBatching']) {
147+
if ($matrixConfig.PSObject.Properties['PRBatching'] -and $matrixResults) {
148148
# if we are doing a PR Batch, we need to just add the matrix items directly to the OverallResult
149149
# as the users have explicitly disabled PR batching for this matrix.
150150
if (!$matrixConfig.PRBatching) {

sdk/core/azure-core/CLIENT_LIBRARY_DEVELOPER.md

Lines changed: 137 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,27 @@
1-
21
# Azure Core Library - Client library developer reference
32

3+
## Table of contents
4+
5+
- [Pipeline](#pipeline)
6+
- [Pipeline client configurations](#pipeline-client-configurations)
7+
- [Transport](#transport)
8+
- [Proxy Settings](#proxy-settings)
9+
- [HttpRequest and HttpResponse](#httprequest-and-httpresponse)
10+
- [PipelineRequest and PipelineResponse](#pipelinerequest-and-pipelineresponse)
11+
- [Policies](#policies)
12+
- [SansIOHTTPPolicy](#sansiohttppolicy)
13+
- [HTTPPolicy and AsyncHTTPPolicy](#httppolicy-and-asynchttppolicy)
14+
- [Available Policies](#available-policies)
15+
- [The Pipeline](#the-pipeline)
16+
- [Credentials](#credentials)
17+
- [Token Credential Protocols](#token-credential-protocols)
18+
- [SupportsTokenInfo and AsyncSupportsTokenInfo protocols (preferred)](#supportstokeninfo-and-asyncsupportstokeninfo-protocols-preferred)
19+
- [TokenCredential protocol (legacy)](#tokencredential-protocol-legacy)
20+
- [Implementation Guidance](#implementation-guidance)
21+
- [Known uses of token request parameters](#known-uses-of-token-request-parameters)
22+
- [BearerTokenCredentialPolicy and AsyncBearerTokenCredentialPolicy](#bearertokencredentialpolicy-and-asyncbearertokencredentialpolicy)
23+
- [Long-running operation (LRO) customization](#long-running-operation-lro-customization)
24+
425
## Pipeline
526

627
The Azure Core pipeline is a re-structuring of the msrest pipeline introduced in msrest 0.6.0.
@@ -544,10 +565,67 @@ class Pipeline:
544565

545566
## Credentials
546567

547-
### TokenCredential protocol
568+
### Token Credential Protocols
569+
570+
Clients from the Azure SDK often require a credential instance in their constructors. Azure Core offers several protocols for credential types that provide OAuth tokens. The main protocols are `SupportsTokenInfo` (preferred) and `TokenCredential` (legacy).
571+
572+
#### SupportsTokenInfo and AsyncSupportsTokenInfo protocols (preferred)
573+
574+
These protocols are the preferred way to implement new credential types in the Azure SDK. They are capable of providing enhanced token information and also provide a more structured approach to token requests compared to the legacy `TokenCredential` protocol. New credential implementations should aim to implement these protocols.
575+
576+
The `SupportsTokenInfo` protocol specifies a class that implements `get_token_info` which returns an `AccessTokenInfo` object which contains the token string, its expiration time, and additional properties such as `refresh_on` and `token_type`.
577+
578+
```python
579+
class AccessTokenInfo:
580+
"""Information about an OAuth access token.
581+
582+
This class is an alternative to `AccessToken` which provides additional
583+
information about the token.
584+
"""
585+
586+
token: str
587+
"""The token string."""
588+
expires_on: int
589+
"""The token's expiration time in Unix time."""
590+
token_type: str
591+
"""The type of access token."""
592+
refresh_on: Optional[int]
593+
"""Specifies the time, in Unix time, when the cached token should be proactively
594+
refreshed. Optional."""
595+
596+
597+
class SupportsTokenInfo(Protocol, ContextManager["SupportsTokenInfo"]):
598+
"""Protocol for classes able to provide OAuth access tokens with additional properties."""
599+
600+
def get_token_info(self, *scopes: str, options: Optional[TokenRequestOptions] = None) -> AccessTokenInfo:
601+
"""Request an access token for `scopes`.
602+
603+
:param str scopes: The type of access needed.
604+
:keyword options: A dictionary of options for the token request. Unknown options will be ignored.
605+
:paramtype options: TokenRequestOptions
606+
607+
:rtype: AccessTokenInfo
608+
:return: An AccessTokenInfo instance containing information about the token.
609+
"""
610+
611+
def close(self) -> None:
612+
...
613+
```
614+
615+
The async version `AsyncSupportsTokenInfo` provides the same functionality but with async/await support and requires implementation of the async context manager protocol (`__aenter__`, `__aexit__`, and `close` methods should be implemented).
616+
617+
The `get_token_info` methods use `TokenRequestOptions` to handle token request parameters in a more structured way:
618+
619+
```python
620+
class TokenRequestOptions(TypedDict, total=False):
621+
claims: str # Additional claims required in the token
622+
tenant_id: str # The tenant ID for the token request
623+
enable_cae: bool # Whether to enable Continuous Access Evaluation
624+
```
625+
626+
#### TokenCredential protocol (legacy)
548627

549-
Clients from the Azure SDK often require a `TokenCredential` instance in their constructors. A `TokenCredential` is
550-
meant to provide OAuth tokens to authenticate service requests and can be implemented in a number of ways.
628+
While still supported, the `TokenCredential` protocol is considered legacy as it has extensibility limitations. It is recommended to use `SupportsTokenInfo` for new credential implementations. Generally, to ensure compatibility with existing clients, new credential implementations should implement both `SupportsTokenInfo` and `TokenCredential`.
551629

552630
The `TokenCredential` protocol specifies a class that has a single method -- `get_token` -- which returns an
553631
`AccessToken`: a `NamedTuple` containing a `token` string and an `expires_on` integer (in Unix time).
@@ -559,7 +637,12 @@ class TokenCredential(Protocol):
559637
"""Protocol for classes able to provide OAuth tokens."""
560638

561639
def get_token(
562-
self, *scopes: str, claims: Optional[str] = None, tenant_id: Optional[str] = None, **kwargs: Any
640+
self,
641+
*scopes: str,
642+
claims: Optional[str] = None,
643+
tenant_id: Optional[str] = None,
644+
enable_cae: bool = False,
645+
**kwargs: Any,
563646
) -> AccessToken:
564647
"""Request an access token for `scopes`.
565648
@@ -576,22 +659,56 @@ class TokenCredential(Protocol):
576659
"""
577660
```
578661

579-
A `TokenCredential` implementation needs to implement the `get_token` method to these specifications and can optionally
580-
implement additional methods. The [`azure-identity`][identity_github] package has a number of `TokenCredential`
581-
implementations that can be used for reference. For example, the [`InteractiveCredential`][interactive_cred] is used as
582-
a base class for multiple credentials and uses `claims` and `tenant_id` in token requests.
662+
The async version `AsyncTokenCredential` provides the same functionality but with async/await support and also requires implementation of the async context manager protocol (`__aenter__`, `__aexit__`, and `close` methods should be implemented).
663+
664+
If a `TokenCredential` implementation doesn't have a use for a keyword argument in a given scenario, the documentation for the implementation should mention that this keyword argument will not be used when making token requests, as well as any potential consequences of this. For example, if a `TokenCredential` implementation doesn't use `tenant_id`, it should document that fetched tokens may not authorize requests made to the specified tenant.
665+
666+
When implementing the `get_token` method, ensure all keyword arguments are explicitly defined rather than using `**kwargs`. This approach prevents unintended keyword arguments from being passed to the HTTP transport layer, which could lead to unexpected behavior. The `get_token_info` method in `SupportsTokenInfo` does not have this issue, as it uses a `TokenRequestOptions` object to handle token request parameters.
667+
668+
### Implementation Guidance
583669

584-
If a `TokenCredential` implementation doesn't have a use for a keyword argument in a given scenario, the unused
585-
keyword argument should be removed from `kwargs` before getting passed elsewhere. The documentation for the
586-
implementation should mention that this keyword argument will not be used when making token requests, as well as any
587-
potential consequences of this. For example, if a `TokenCredential` implementation doesn't use `tenant_id`, it should
588-
document that fetched tokens may not authorize requests made to the specified tenant.
670+
When implementing a new credential type:
671+
672+
1. Implement `SupportsTokenInfo`/`AsyncSupportsTokenInfo` as your primary protocol
673+
2. If backwards compatibility is needed:
674+
- Implement `TokenCredential`/`AsyncTokenCredential` as a secondary protocol
675+
- Convert `get_token_info` results to `AccessToken` in the `get_token` implementation
676+
677+
Example structure for a new credential implementation:
678+
679+
```python
680+
from azure.core.credentials import AccessToken, AccessTokenInfo, TokenRequestOptions
681+
682+
683+
class MyNewCredential(SupportsTokenInfo):
684+
def get_token_info(self, *scopes: str, options: Optional[TokenRequestOptions] = None) -> AccessTokenInfo:
685+
# Primary implementation
686+
...
687+
688+
def get_token(
689+
self,
690+
*scopes: str,
691+
claims: Optional[str] = None,
692+
tenant_id: Optional[str] = None,
693+
enable_cae: bool = False,
694+
**kwargs: Any,
695+
) -> AccessToken:
696+
697+
# Secondary implementation for backwards compatibility
698+
options: TokenRequestOptions = {}
699+
if tenant_id:
700+
options["tenant_id"] = tenant_id
701+
if claims:
702+
options["claims"] = claims
703+
options["enable_cae"] = enable_cae
704+
705+
token_info = self.get_token_info(*scopes, options=options)
706+
return AccessToken(token_info.token, token_info.expires_on)
707+
```
589708

590-
There is also an async protocol -- the `AsyncTokenCredential` protocol -- that specifies a class with an async
591-
`get_token` method with the same arguments. An `AsyncTokenCredential` implementation additionally needs to be a context
592-
manager, with `__aenter__`, `__aexit__`, and `close` methods.
709+
The [`azure-identity`][identity_github] package has a number of credentials that implement both protocols, and serves as a good reference for implementing new credentials. For example, the [`InteractiveCredential`][interactive_cred] is used as a base class for multiple credentials and uses `claims` and `tenant_id` in token requests.
593710

594-
#### Known uses of `get_token` keyword-only parameters
711+
### Known uses of token request parameters
595712

596713
**`claims`**
597714

@@ -613,9 +730,9 @@ manager, with `__aenter__`, `__aexit__`, and `close` methods.
613730

614731
### BearerTokenCredentialPolicy and AsyncBearerTokenCredentialPolicy
615732

616-
`BearerTokenCredentialPolicy` and `AsyncBearerTokenCredentialPolicy` are HTTP policies that are used to authenticate requests to services that accept bearer tokens in their authorization headers. These credential policies take a `TokenCredential` instance and scopes as parameters in their constructors. The `TokenCredential` instance is used to get an access token for the scopes, and the policy adds the token to the request's authorization header.
733+
`BearerTokenCredentialPolicy` and `AsyncBearerTokenCredentialPolicy` are HTTP policies that are used to authenticate requests to services that accept bearer tokens in their authorization headers. These credential policies take `SupportsTokenInfo`/`TokenCredential` instances and scopes as parameters in their constructors. The `SupportsTokenInfo`/`TokenCredential` instance is used to get an access token for the scopes, and the policy adds the token to the request's authorization header.
617734

618-
Both of these policies also accept an `enable_cae` keyword argument that is passed to the `TokenCredential` instance's `get_token` method if set to `True`. This argument is used to indicate that the requested token should be [CAE-enabled][cae_doc]. If an SDK's service supports CAE, it should set this value to `True` when creating the policy.
735+
Both of these policies also accept an `enable_cae` keyword argument that is passed to the `SupportsTokenInfo`/`TokenCredential` instance's `get_token_info`/`get_token` method if set to `True`. This argument is used to indicate that the requested token should be [CAE-enabled][cae_doc]. If an SDK's service supports CAE, it should set this value to `True` when creating the policy.
619736

620737
```python
621738
from azure.core.pipeline.policies import BearerTokenCredentialPolicy

0 commit comments

Comments
 (0)