From a6dc38385a51a4707cf8ee80d8cb9ef4d2091207 Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Tue, 18 Mar 2025 18:06:47 -0500 Subject: [PATCH 01/10] migrate release pipeline --- eng/ci/extension-release.yml | 33 +++ .../official/jobs/publish-release.yml | 272 ++++++++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 eng/ci/extension-release.yml create mode 100644 eng/templates/official/jobs/publish-release.yml diff --git a/eng/ci/extension-release.yml b/eng/ci/extension-release.yml new file mode 100644 index 0000000..987569c --- /dev/null +++ b/eng/ci/extension-release.yml @@ -0,0 +1,33 @@ +pr: none + +resources: + repositories: + - repository: 1es + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release + - repository: eng + type: git + name: engineering + ref: refs/tags/release + +variables: + - name: codeql.excludePathPatterns + value: deps/,build/ + +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1es + parameters: + pool: + name: 1es-pool-azfunc + image: 1es-windows-2022 + os: windows + sdl: + codeSignValidation: + enabled: true + break: true + + stages: + - stage: Release + jobs: + - template: /eng/templates/official/jobs/publish-release.yml@self diff --git a/eng/templates/official/jobs/publish-release.yml b/eng/templates/official/jobs/publish-release.yml new file mode 100644 index 0000000..008f1f6 --- /dev/null +++ b/eng/templates/official/jobs/publish-release.yml @@ -0,0 +1,272 @@ +jobs: + +- job: "CreateReleaseBranch" + displayName: 'Create Release Branch' + pool: + name: 1es-pool-azfunc + image: 1es-ubuntu-22.04 + os: linux + steps: + - powershell: | + $githubToken = "$(GithubPat)" + $newLibraryVersion = "$(NewLibraryVersion)" + $extensionName= "$(ExtensionName)" + $extensionDirectory = "$(ExtensionDirectory)" + + if($newLibraryVersion -match '(\d)+.(\d)+.*') { + # Create GitHub credential + git config --global user.name "AzureFunctionsPython" + git config --global user.email "azfunc@microsoft.com" + + # Heading to Artifact Repository + Write-Host "Operating based on $stagingDirectory/azure-functions-python-extensions" + git checkout -b "$extensionName/release/$newLibraryVersion" + + # Change __init__.py version + Write-Host "Change version number in $extensionDirectory __init__.py to $newLibraryVersion" + ((Get-Content $extensionName/$extensionDirectory/__init__.py) -replace "__version__ = '(\d)+.(\d)+.*'", "__version__ = '$newLibraryVersion'" -join "`n") + "`n" | Set-Content -NoNewline $extensionName/$extensionDirectory/__init__.py + git add $extensionName/$extensionDirectory/__init__.py + git commit -m "build: update $extensionName Version to $newLibraryVersion" + + # Create release branch release/X.Y.Z + Write-Host "Creating release branch release/$newLibraryVersion" + git push --repo="https://$githubToken@github.com/Azure/azure-functions-python-extensions.git" + } else { + Write-Host "NewLibraryVersion $newLibraryVersion is malformed (example: 1.5.0)" + exit -1 + } + displayName: 'Push release/x.y.z' + +- job: "CreateReleaseTag" + dependsOn: ['CheckReleaseBranch'] + displayName: 'Check Release Branch' + pool: server + steps: + - task: ManualValidation@1 + displayName: '(Optional) Modify release/x.y.z branch' + inputs: + notifyUsers: '' # No email notifications sent + instructions: | + 1. Check if the https://github.com/Azure/azure-functions-python-extensions/tree/release/$(NewLibraryVersion) passes all unit test. + 2. If not, modify the release/$(NewLibraryVersion) branch. + 3. Ensure release/$(NewLibraryVersion) branch contains all necessary changes. + +- job: "CheckReleaseTag" + dependsOn: ['CreateReleaseBranch'] + displayName: 'Check Release Tag' + steps: + - powershell: | + $githubToken = "$(GithubPat)" + $newLibraryVersion = "$(NewLibraryVersion)" + $githubToken = "$(GithubPat)" + $extensionName= "$(ExtensionName)" + $extensionDirectory = "$(ExtensionDirectory)" + + + if($newLibraryVersion -match '(\d)+.(\d)+.(\d)+') { + # Create GitHub credential + git config --global user.name "AzureFunctionsPython" + git config --global user.email "funcdisc@microsoft.com" + + # Clone Repository + git clone https://$githubToken@github.com/Azure/azure-functions-python-extensions + Write-Host "Cloned azure-functions-python-extensions into local" + Set-Location "azure-functions-python-extensions" + git checkout "origin/$extensionName/release/$newLibraryVersion" + + # Create release tag X.Y.Z + Write-Host "Creating release tag $newLibraryVersion" + git tag -a "$newLibraryVersion" -m "$newLibraryVersion" + + # Push tag to remote + git push origin $newLibraryVersion + } else { + Write-Host "NewLibraryVersion $newLibraryVersion is malformed (example: 1.5.0)" + exit -1 + } + displayName: 'Tag and push x.y.z' + - powershell: | + $githubUser = "$(GithubUser)" + $githubToken = "$(GithubPat)" + $newLibraryVersion = "$(NewLibraryVersion)" + $extensionName= "$(ExtensionName)" + $extensionDirectory = "$(ExtensionDirectory)" + + if($newLibraryVersion -match '(\d)+.(\d)+.(\d)+') { + # Create GitHub credential + $credential = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("${githubUser}:${githubToken}")) + + # Create Release Note + Write-Host "Creating release note in GitHub" + $body = (@{tag_name="$newLibraryVersion";name="Release $newLibraryVersion";body="- Fill in Release Note Here";draft=$true} | ConvertTo-Json -Compress) + $response = Invoke-WebRequest -Headers @{"Cache-Control"="no-cache";"Content-Type"="application/json";"Authorization"="Basic $credential"} -Method Post -Body "$body" -Uri "https://api.github.com/repos/Azure/azure-functions-python-extensions/releases" + + # Return Value + if ($response.StatusCode -ne 201) { + Write-Host "Failed to create release note in GitHub" + exit -1 + } + + $draftUrl = $response | ConvertFrom-Json | Select -expand url + Write-Host "Release draft created in $draftUrl" + } else { + Write-Host "NewLibraryVersion $newLibraryVersion is malformed (example: 1.1.8)" + exit -1 + } + displayName: 'Create GitHub release draft' + +- job: "CheckGitHubRelease" + dependsOn: ['CreateReleaseTag'] + displayName: '(Manual) Check GitHub release note' + pool: server + steps: + - task: ManualValidation@1 + displayName: 'Write GitHub release note' + inputs: + notifyUsers: '' + instructions: 'Please head to https://github.com/Azure/azure-functions-python-extensions/releases to finish the release note' + +- job: "CreateWorkerPR" + dependsOn: ['CheckGitHubRelease'] + displayName: 'Create PR in Worker Repo' + steps: + - powershell: | + $githubUser = "$(GithubUser)" + $githubToken = "$(GithubPat)" + $newLibraryVersion = "$(NewLibraryVersion)" + $extensionName= "$(ExtensionName)" + $extensionDirectory = "$(ExtensionDirectory)" + $newBranch = "extensions/$(ExtensionName)/$newLibraryVersion" + + if($newLibraryVersion -match '(\d)+.(\d)+.*') { + # Create GitHub credential + git config --global user.name "AzureFunctionsPython" + git config --global user.email "funcdisc@microsoft.com" + + # Create GitHub credential + $credential = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("${githubUser}:${githubToken}")) + + # Clone Repository + git clone https://$githubToken@github.com/Azure/azure-functions-python-worker + Write-Host "Cloned azure-functions-python-worker into local and checkout $newBranch branch" + Set-Location "azure-functions-python-worker" + git checkout -b $newBranch "origin/dev" + + # Modify Extension $extensionName Version in pyproject.toml + Write-Host "Replacing Extension $extensionName version in worker's pyproject.toml" + ((Get-Content pyproject.toml) -replace '"$extensionName=='(\d)+.(\d)+.*'"','"$extensionName==$newLibraryVersion"' -join "`n") + "`n" | Set-Content -NoNewline pyproject.toml + + # Commit Python Version + Write-Host "Pushing $newBranch to azure-functions-python-worker repo" + git add pyproject.toml + git commit -m "build: update $extensionName version to $newLibraryVersion" + git push origin $newBranch + + # Create PR + Write-Host "Creating PR draft in GitHub" + $body = (@{head="$newBranch";base="dev";body="Python Extension [$extensionName] Version [$newLibraryVersion](https://github.com/Azure/azure-functions-python-extensions/releases/tag/$extensionName/$newLibraryVersion)";draft=$true;maintainer_can_modify=$true;title="Update Python Extension $extensionName Version to $newLibraryVersion"} | ConvertTo-Json -Compress) + $response = Invoke-WebRequest -Headers @{"Cache-Control"="no-cache";"Content-Type"="application/json";"Authorization"="Basic $credential";"Accept"="application/vnd.github.v3+json"} -Method Post -Body "$body" -Uri "https://api.github.com/repos/Azure/azure-functions-python-worker/pulls" + + # Return Value + if ($response.StatusCode -ne 201) { + Write-Host "Failed to create PR in Azure Functions Python Worker" + exit -1 + } + + $draftUrl = $response | ConvertFrom-Json | Select -expand url + Write-Host "PR draft created in $draftUrl" + } else { + Write-Host "NewLibraryVersion $newLibraryVersion is malformed (example: 1.1.8)" + exit -1 + } + displayName: 'Create PR in Worker Repo' + +- job: "FinishWorkerPr" + dependsOn: ['CreateWorkerPR'] + displayName: '(Manual) Finish Worker PR' + pool: server + steps: + - task: ManualValidation@1 + displayName: 'Wait For Python Worker Build' + inputs: + notifyUsers: '' + instructions: | + 1. Please wait and check if all goes green in the https://github.com/Azure/azure-functions-python-worker/pulls + 2. Merge the PR into worker dev branch + +- job: "PyPIPackage" + dependsOn: ['FinishWorkerPr'] + displayName: 'PyPI Package' + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Python Extension release/.x.y.z Artifact' + inputs: + buildType: specific + project: '3f99e810-c336-441f-8892-84983093ad7f' + definition: 798 + specificBuildWithTriggering: true + buildVersionToDownload: latestFromBranch + branchName: refs/heads/dev + targetPath: PythonExtensionArtifact + - task: UsePythonVersion@0 + displayName: 'Use Python 3.11' + inputs: + versionSpec: 3.11 + - powershell: | + $newLibraryVersion = "$(NewLibraryVersion)" + $pypiToken = "$(PypiToken)" + $extensionName= "$(ExtensionName)" + $extensionDirectory = "$(ExtensionDirectory)" + + + # Setup local Python environment + Write-Host "Setup local Python environment" + python -m pip install -U pip + pip install twine + + # Build wheels and project + Write-Host "Build Wheels and Upload to https://upload.pypi.org/project/$extensionName/$newLibraryVersion/" + twine upload --repository-url https://upload.pypi.org/legacy/ --username "__token__" --password "$pypiToken" PythonExtensionArtifact/$extensionName/$extensionName/dist/* + Start-Sleep -Seconds 3 + + # Checking if the new version is uploaded + Write-Host "Check if new version is uploaded" + $response = Invoke-WebRequest -Headers @{"Cache-Control"="no-cache"} -Method Get -Uri "https://pypi.org/project/$extensionName/$newLibraryVersion/" + + # Return Value + if ($response.StatusCode -ne 200) { + Write-Host "Failed to verify https://pypi.org/project/$extensionName/$newLibraryVersion/" + exit -1 + } + displayName: 'Publish package to pypi.org' + +- job: "MergeBack" + dependsOn: ['PyPIPackage'] + displayName: 'Merge Back' + steps: + - powershell: | + $githubToken = "$(GithubPat)" + $newLibraryVersion = "$(newLibraryVersion)" + $extensionName= "$(ExtensionName)" + $extensionDirectory = "$(ExtensionDirectory)" + + if($newLibraryVersion -match '(\d)+.(\d)+.(\d)+') { + # Create GitHub credential + git config --global user.name "AzureFunctionsPython" + git config --global user.email "funcdisc@microsoft.com" + + # Clone Repository + git clone https://$githubToken@github.com/Azure/azure-functions-python-extensions + Write-Host "Cloned azure-functions-python-extensions into local" + Set-Location "azure-functions-python-extensions" + + # Merge back to dev + Write-Host "Merging $extensionName/release/$newLibraryVersion back to dev" + git checkout dev + git merge "origin/$extensionName/release/$newLibraryVersion" + git push origin dev + } else { + Write-Host "newLibraryVersion $newLibraryVersion is malformed (example: 1.1.8)" + exit -1 + } + displayName: 'Merge release/x.y.z back to dev' From 711ad8a09c53a71e5b43616ff74721b653da9afb Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Thu, 20 Mar 2025 12:37:35 -0500 Subject: [PATCH 02/10] small fixes --- .../extensions/base/__init__.py | 2 +- .../extensions/bindings/blob/__init__.py | 2 +- .../extensions/http/fastapi/__init__.py | 2 +- .../official/jobs/publish-release.yml | 24 +++++++++---------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/azurefunctions-extensions-base/azurefunctions/extensions/base/__init__.py b/azurefunctions-extensions-base/azurefunctions/extensions/base/__init__.py index fa764ca..1396b7a 100644 --- a/azurefunctions-extensions-base/azurefunctions/extensions/base/__init__.py +++ b/azurefunctions-extensions-base/azurefunctions/extensions/base/__init__.py @@ -39,4 +39,4 @@ "RequestSynchronizer", ] -__version__ = "1.0.0b2" +__version__ = '1.0.0b2' diff --git a/azurefunctions-extensions-bindings-blob/azurefunctions/extensions/bindings/blob/__init__.py b/azurefunctions-extensions-bindings-blob/azurefunctions/extensions/bindings/blob/__init__.py index 1608985..e5f8fd0 100644 --- a/azurefunctions-extensions-bindings-blob/azurefunctions/extensions/bindings/blob/__init__.py +++ b/azurefunctions-extensions-bindings-blob/azurefunctions/extensions/bindings/blob/__init__.py @@ -13,4 +13,4 @@ "BlobClientConverter", ] -__version__ = "1.0.0b3" +__version__ = '1.0.0b3' diff --git a/azurefunctions-extensions-http-fastapi/azurefunctions/extensions/http/fastapi/__init__.py b/azurefunctions-extensions-http-fastapi/azurefunctions/extensions/http/fastapi/__init__.py index ec695e7..d3dca27 100644 --- a/azurefunctions-extensions-http-fastapi/azurefunctions/extensions/http/fastapi/__init__.py +++ b/azurefunctions-extensions-http-fastapi/azurefunctions/extensions/http/fastapi/__init__.py @@ -28,4 +28,4 @@ "FileResponse", ] -__version__ = "1.0.0b1" +__version__ = '1.0.0b1' diff --git a/eng/templates/official/jobs/publish-release.yml b/eng/templates/official/jobs/publish-release.yml index 008f1f6..8deeeb7 100644 --- a/eng/templates/official/jobs/publish-release.yml +++ b/eng/templates/official/jobs/publish-release.yml @@ -37,9 +37,9 @@ jobs: } displayName: 'Push release/x.y.z' -- job: "CreateReleaseTag" - dependsOn: ['CheckReleaseBranch'] - displayName: 'Check Release Branch' +- job: "CheckReleaseBranch" + dependsOn: ['CreateReleaseBranch'] + displayName: '(Manual) Check Release Branch' pool: server steps: - task: ManualValidation@1 @@ -51,9 +51,9 @@ jobs: 2. If not, modify the release/$(NewLibraryVersion) branch. 3. Ensure release/$(NewLibraryVersion) branch contains all necessary changes. -- job: "CheckReleaseTag" - dependsOn: ['CreateReleaseBranch'] - displayName: 'Check Release Tag' +- job: "CreateReleaseTag" + dependsOn: ['CheckReleaseBranch'] + displayName: 'Create Release Tag' steps: - powershell: | $githubToken = "$(GithubPat)" @@ -76,10 +76,10 @@ jobs: # Create release tag X.Y.Z Write-Host "Creating release tag $newLibraryVersion" - git tag -a "$newLibraryVersion" -m "$newLibraryVersion" + git tag -a "$extensionName/$newLibraryVersion" -m "$newLibraryVersion" # Push tag to remote - git push origin $newLibraryVersion + git push origin $extensionName/$newLibraryVersion } else { Write-Host "NewLibraryVersion $newLibraryVersion is malformed (example: 1.5.0)" exit -1 @@ -98,7 +98,7 @@ jobs: # Create Release Note Write-Host "Creating release note in GitHub" - $body = (@{tag_name="$newLibraryVersion";name="Release $newLibraryVersion";body="- Fill in Release Note Here";draft=$true} | ConvertTo-Json -Compress) + $body = (@{tag_name="$newLibraryVersion";name="$extensionName $newLibraryVersion";body="- Fill in Release Note Here";draft=$true} | ConvertTo-Json -Compress) $response = Invoke-WebRequest -Headers @{"Cache-Control"="no-cache";"Content-Type"="application/json";"Authorization"="Basic $credential"} -Method Post -Body "$body" -Uri "https://api.github.com/repos/Azure/azure-functions-python-extensions/releases" # Return Value @@ -125,7 +125,7 @@ jobs: inputs: notifyUsers: '' instructions: 'Please head to https://github.com/Azure/azure-functions-python-extensions/releases to finish the release note' - + - job: "CreateWorkerPR" dependsOn: ['CheckGitHubRelease'] displayName: 'Create PR in Worker Repo' @@ -154,7 +154,7 @@ jobs: # Modify Extension $extensionName Version in pyproject.toml Write-Host "Replacing Extension $extensionName version in worker's pyproject.toml" - ((Get-Content pyproject.toml) -replace '"$extensionName=='(\d)+.(\d)+.*'"','"$extensionName==$newLibraryVersion"' -join "`n") + "`n" | Set-Content -NoNewline pyproject.toml + ((Get-Content pyproject.toml) -replace '"$(ExtensionName)==(\d)+.(\d)+.*"','"$(ExtensionName)==$(NewLibraryVersion)"' -join "`n") + "`n" | Set-Content -NoNewline pyproject.toml # Commit Python Version Write-Host "Pushing $newBranch to azure-functions-python-worker repo" @@ -164,7 +164,7 @@ jobs: # Create PR Write-Host "Creating PR draft in GitHub" - $body = (@{head="$newBranch";base="dev";body="Python Extension [$extensionName] Version [$newLibraryVersion](https://github.com/Azure/azure-functions-python-extensions/releases/tag/$extensionName/$newLibraryVersion)";draft=$true;maintainer_can_modify=$true;title="Update Python Extension $extensionName Version to $newLibraryVersion"} | ConvertTo-Json -Compress) + $body = (@{head="$newBranch";base="dev";body="Python Extension [$extensionName] Version [$newLibraryVersion](https://github.com/Azure/azure-functions-python-extensions/releases/tag/$extensionName/$newLibraryVersion)";draft=$true;maintainer_can_modify=$true;title="buikld: update $extensionName version to $newLibraryVersion"} | ConvertTo-Json -Compress) $response = Invoke-WebRequest -Headers @{"Cache-Control"="no-cache";"Content-Type"="application/json";"Authorization"="Basic $credential";"Accept"="application/vnd.github.v3+json"} -Method Post -Body "$body" -Uri "https://api.github.com/repos/Azure/azure-functions-python-worker/pulls" # Return Value From b3fb4b1c28c6faf902c2300e099a17e612db9df4 Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Thu, 20 Mar 2025 12:39:15 -0500 Subject: [PATCH 03/10] remove spaces, formatting --- .../official/jobs/publish-release.yml | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/eng/templates/official/jobs/publish-release.yml b/eng/templates/official/jobs/publish-release.yml index 8deeeb7..c220ac7 100644 --- a/eng/templates/official/jobs/publish-release.yml +++ b/eng/templates/official/jobs/publish-release.yml @@ -62,7 +62,6 @@ jobs: $extensionName= "$(ExtensionName)" $extensionDirectory = "$(ExtensionDirectory)" - if($newLibraryVersion -match '(\d)+.(\d)+.(\d)+') { # Create GitHub credential git config --global user.name "AzureFunctionsPython" @@ -218,26 +217,25 @@ jobs: $extensionName= "$(ExtensionName)" $extensionDirectory = "$(ExtensionDirectory)" - - # Setup local Python environment - Write-Host "Setup local Python environment" - python -m pip install -U pip - pip install twine - - # Build wheels and project - Write-Host "Build Wheels and Upload to https://upload.pypi.org/project/$extensionName/$newLibraryVersion/" - twine upload --repository-url https://upload.pypi.org/legacy/ --username "__token__" --password "$pypiToken" PythonExtensionArtifact/$extensionName/$extensionName/dist/* - Start-Sleep -Seconds 3 - - # Checking if the new version is uploaded - Write-Host "Check if new version is uploaded" - $response = Invoke-WebRequest -Headers @{"Cache-Control"="no-cache"} -Method Get -Uri "https://pypi.org/project/$extensionName/$newLibraryVersion/" - - # Return Value - if ($response.StatusCode -ne 200) { - Write-Host "Failed to verify https://pypi.org/project/$extensionName/$newLibraryVersion/" - exit -1 - } + # Setup local Python environment + Write-Host "Setup local Python environment" + python -m pip install -U pip + pip install twine + + # Build wheels and project + Write-Host "Build Wheels and Upload to https://upload.pypi.org/project/$extensionName/$newLibraryVersion/" + twine upload --repository-url https://upload.pypi.org/legacy/ --username "__token__" --password "$pypiToken" PythonExtensionArtifact/$extensionName/$extensionName/dist/* + Start-Sleep -Seconds 3 + + # Checking if the new version is uploaded + Write-Host "Check if new version is uploaded" + $response = Invoke-WebRequest -Headers @{"Cache-Control"="no-cache"} -Method Get -Uri "https://pypi.org/project/$extensionName/$newLibraryVersion/" + + # Return Value + if ($response.StatusCode -ne 200) { + Write-Host "Failed to verify https://pypi.org/project/$extensionName/$newLibraryVersion/" + exit -1 + } displayName: 'Publish package to pypi.org' - job: "MergeBack" From ac17a46cc31038e29824fbc9e82aa944396077c5 Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Thu, 20 Mar 2025 15:23:34 -0500 Subject: [PATCH 04/10] update email --- eng/templates/official/jobs/publish-release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eng/templates/official/jobs/publish-release.yml b/eng/templates/official/jobs/publish-release.yml index c220ac7..72895de 100644 --- a/eng/templates/official/jobs/publish-release.yml +++ b/eng/templates/official/jobs/publish-release.yml @@ -65,7 +65,7 @@ jobs: if($newLibraryVersion -match '(\d)+.(\d)+.(\d)+') { # Create GitHub credential git config --global user.name "AzureFunctionsPython" - git config --global user.email "funcdisc@microsoft.com" + git config --global user.email "azfunc@microsoft.com" # Clone Repository git clone https://$githubToken@github.com/Azure/azure-functions-python-extensions @@ -140,7 +140,7 @@ jobs: if($newLibraryVersion -match '(\d)+.(\d)+.*') { # Create GitHub credential git config --global user.name "AzureFunctionsPython" - git config --global user.email "funcdisc@microsoft.com" + git config --global user.email "azfunc@microsoft.com" # Create GitHub credential $credential = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("${githubUser}:${githubToken}")) @@ -251,7 +251,7 @@ jobs: if($newLibraryVersion -match '(\d)+.(\d)+.(\d)+') { # Create GitHub credential git config --global user.name "AzureFunctionsPython" - git config --global user.email "funcdisc@microsoft.com" + git config --global user.email "azfunc@microsoft.com" # Clone Repository git clone https://$githubToken@github.com/Azure/azure-functions-python-extensions From 6fd0e7f1877e81feecb9a9ab965f38ef3437470a Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Thu, 20 Mar 2025 15:35:26 -0500 Subject: [PATCH 05/10] lint fix --- azurefunctions-extensions-base/pyproject.toml | 2 ++ azurefunctions-extensions-bindings-blob/pyproject.toml | 2 ++ azurefunctions-extensions-http-fastapi/pyproject.toml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/azurefunctions-extensions-base/pyproject.toml b/azurefunctions-extensions-base/pyproject.toml index 89fb18e..a81e2a3 100644 --- a/azurefunctions-extensions-base/pyproject.toml +++ b/azurefunctions-extensions-base/pyproject.toml @@ -40,3 +40,5 @@ version = {attr = "azurefunctions.extensions.base.__version__"} [tool.setuptools.packages.find] exclude = ['azurefunctions.extensions', 'azurefunctions', 'tests'] +[tool.black] +skip-string-normalization = true diff --git a/azurefunctions-extensions-bindings-blob/pyproject.toml b/azurefunctions-extensions-bindings-blob/pyproject.toml index 03b86de..f7c02b1 100644 --- a/azurefunctions-extensions-bindings-blob/pyproject.toml +++ b/azurefunctions-extensions-bindings-blob/pyproject.toml @@ -47,3 +47,5 @@ exclude = [ 'azurefunctions', 'tests', 'samples' ] +[tool.black] +skip-string-normalization = true diff --git a/azurefunctions-extensions-http-fastapi/pyproject.toml b/azurefunctions-extensions-http-fastapi/pyproject.toml index 66a12a1..17b8663 100644 --- a/azurefunctions-extensions-http-fastapi/pyproject.toml +++ b/azurefunctions-extensions-http-fastapi/pyproject.toml @@ -49,3 +49,5 @@ exclude = [ 'azurefunctions', 'tests', 'samples' ] +[tool.black] +skip-string-normalization = true From 2d045177bc26a70948ce8fb87dd84d4142d669d8 Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Thu, 20 Mar 2025 15:41:55 -0500 Subject: [PATCH 06/10] fix lint --- .github/linters/.isort.cfg | 1 + azurefunctions-extensions-base/pyproject.toml | 3 --- azurefunctions-extensions-bindings-blob/pyproject.toml | 3 --- azurefunctions-extensions-http-fastapi/pyproject.toml | 3 --- 4 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/linters/.isort.cfg b/.github/linters/.isort.cfg index f238bf7..7da5d99 100644 --- a/.github/linters/.isort.cfg +++ b/.github/linters/.isort.cfg @@ -1,2 +1,3 @@ [settings] profile = black +skip_string_normalization = true \ No newline at end of file diff --git a/azurefunctions-extensions-base/pyproject.toml b/azurefunctions-extensions-base/pyproject.toml index a81e2a3..8554675 100644 --- a/azurefunctions-extensions-base/pyproject.toml +++ b/azurefunctions-extensions-base/pyproject.toml @@ -39,6 +39,3 @@ version = {attr = "azurefunctions.extensions.base.__version__"} [tool.setuptools.packages.find] exclude = ['azurefunctions.extensions', 'azurefunctions', 'tests'] - -[tool.black] -skip-string-normalization = true diff --git a/azurefunctions-extensions-bindings-blob/pyproject.toml b/azurefunctions-extensions-bindings-blob/pyproject.toml index f7c02b1..b2b919b 100644 --- a/azurefunctions-extensions-bindings-blob/pyproject.toml +++ b/azurefunctions-extensions-bindings-blob/pyproject.toml @@ -46,6 +46,3 @@ exclude = [ 'azurefunctions.extensions.bindings','azurefunctions.extensions', 'azurefunctions', 'tests', 'samples' ] - -[tool.black] -skip-string-normalization = true diff --git a/azurefunctions-extensions-http-fastapi/pyproject.toml b/azurefunctions-extensions-http-fastapi/pyproject.toml index 17b8663..96581ab 100644 --- a/azurefunctions-extensions-http-fastapi/pyproject.toml +++ b/azurefunctions-extensions-http-fastapi/pyproject.toml @@ -48,6 +48,3 @@ exclude = [ 'azurefunctions.extensions.http','azurefunctions.extensions', 'azurefunctions', 'tests', 'samples' ] - -[tool.black] -skip-string-normalization = true From d6701e91185b6dc3f22da52d040d1f10066fc909 Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Thu, 20 Mar 2025 18:07:52 -0500 Subject: [PATCH 07/10] fix isort path --- .github/workflows/linting_extension_base.yml | 2 +- .github/workflows/linting_extension_blob.yml | 2 +- .github/workflows/linting_extension_fastapi.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linting_extension_base.yml b/.github/workflows/linting_extension_base.yml index edb051c..830d490 100644 --- a/.github/workflows/linting_extension_base.yml +++ b/.github/workflows/linting_extension_base.yml @@ -75,6 +75,6 @@ jobs: VALIDATE_PYTHON: true VALIDATE_PYTHON_BLACK: true # same as above VALIDATE_PYTHON_ISORT: true # same as above - PYTHON_ISORT_CONFIG_FILE: .isort.cfg + PYTHON_ISORT_CONFIG_FILE: .github/linters/.isort.cfg FILTER_REGEX_INCLUDE: azurefunctions-extensions-base/.* DEFAULT_BRANCH: origin/dev diff --git a/.github/workflows/linting_extension_blob.yml b/.github/workflows/linting_extension_blob.yml index 28892a3..0dcc676 100644 --- a/.github/workflows/linting_extension_blob.yml +++ b/.github/workflows/linting_extension_blob.yml @@ -75,6 +75,6 @@ jobs: VALIDATE_PYTHON: true VALIDATE_PYTHON_BLACK: true # same as above # VALIDATE_PYTHON_ISORT: true # same as above TODO: fix conflicting linters - PYTHON_ISORT_CONFIG_FILE: .isort.cfg + PYTHON_ISORT_CONFIG_FILE: .github/linters/.isort.cfg FILTER_REGEX_INCLUDE: azurefunctions-extensions-bindings-blob/* DEFAULT_BRANCH: origin/dev diff --git a/.github/workflows/linting_extension_fastapi.yml b/.github/workflows/linting_extension_fastapi.yml index 2e770af..61c0098 100644 --- a/.github/workflows/linting_extension_fastapi.yml +++ b/.github/workflows/linting_extension_fastapi.yml @@ -75,6 +75,6 @@ jobs: VALIDATE_PYTHON: true VALIDATE_PYTHON_BLACK: true # same as above VALIDATE_PYTHON_ISORT: true # same as above - PYTHON_ISORT_CONFIG_FILE: .isort.cfg + PYTHON_ISORT_CONFIG_FILE: .github/linters/.isort.cfg FILTER_REGEX_INCLUDE: azurefunctions-extensions-http-fastapi/* DEFAULT_BRANCH: origin/dev From 4af5b1874930bdd88697b6d55fab872917418ba3 Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Thu, 20 Mar 2025 18:56:39 -0500 Subject: [PATCH 08/10] convert lint test to ADO instead of GH actions --- .flake8 | 11 +++ .github/ISSUE_TEMPLATE/bug_report.md | 28 ------- .github/ISSUE_TEMPLATE/feature_request.md | 20 ----- .github/dependabot.yml | 33 -------- .github/linters/.isort.cfg | 3 - .github/linters/tox.ini | 36 --------- .github/workflows/linting_extension_base.yml | 80 ------------------- .github/workflows/linting_extension_blob.yml | 80 ------------------- .../workflows/linting_extension_fastapi.yml | 80 ------------------- .github/workflows/pr_title_enforcer.yml | 20 ----- .../azurefunctions/extensions/base/utils.py | 2 +- azurefunctions-extensions-base/pyproject.toml | 12 +-- .../tests/test_code_quality.py | 51 ++++++++++++ .../extensions/bindings/blob/blobClient.py | 3 +- .../extensions/bindings/blob/utils.py | 3 +- .../pyproject.toml | 2 + .../tests/test_blobclient.py | 11 +-- .../tests/test_code_quality.py | 53 ++++++++++++ .../tests/test_containerclient.py | 11 +-- .../tests/test_ssd.py | 11 +-- .../pyproject.toml | 2 + .../tests/test_code_quality.py | 53 ++++++++++++ .../tests/test_web.py | 8 +- 23 files changed, 206 insertions(+), 407 deletions(-) create mode 100644 .flake8 delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 .github/dependabot.yml delete mode 100644 .github/linters/.isort.cfg delete mode 100644 .github/linters/tox.ini delete mode 100644 .github/workflows/linting_extension_base.yml delete mode 100644 .github/workflows/linting_extension_blob.yml delete mode 100644 .github/workflows/linting_extension_fastapi.yml delete mode 100644 .github/workflows/pr_title_enforcer.yml create mode 100644 azurefunctions-extensions-base/tests/test_code_quality.py create mode 100644 azurefunctions-extensions-bindings-blob/tests/test_code_quality.py create mode 100644 azurefunctions-extensions-http-fastapi/tests/test_code_quality.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..4bb29cf --- /dev/null +++ b/.flake8 @@ -0,0 +1,11 @@ +[flake8] +# it's not a bug that we aren't using all of hacking, ignore: +# H402: Module level import not at top of file +# W503: Line break occurred before a binary operator +# E731: Do not assign a lambda expression, use a def +ignore = W503,E402,E731 + +exclude = .git, __pycache__, build, dist, .eggs, .github, .local, docs/, + Samples, .env*, .vscode, venv*, *.venv* + +max-line-length = 88 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 8bcefe3..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' ---- - -- **Package Name**: -- **Package Version**: -- **Operating System**: -- **Python Version**: - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Additional context** -Add any other context about the problem here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 24473de..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 7f66571..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,33 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file - -version: 2 -updates: - - package-ecosystem: "pip" - # Files stored in repository root - directory: "/azurefunctions-extensions-base" - schedule: - interval: "weekly" - commit-message: - # Prefix all commit messages with "build: " - prefix: "build" - - - package-ecosystem: "pip" - # Files stored in `app` directory - directory: "/azurefunctions-extensions-http-fastapi" - schedule: - interval: "weekly" - commit-message: - # Prefix all commit messages with "build: " - prefix: "build" - - - package-ecosystem: "pip" - # Files stored in `app` directory - directory: "/azurefunctions-extensions-bindings-blob" - schedule: - interval: "weekly" - commit-message: - # Prefix all commit messages with "build: " - prefix: "build" diff --git a/.github/linters/.isort.cfg b/.github/linters/.isort.cfg deleted file mode 100644 index 7da5d99..0000000 --- a/.github/linters/.isort.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[settings] -profile = black -skip_string_normalization = true \ No newline at end of file diff --git a/.github/linters/tox.ini b/.github/linters/tox.ini deleted file mode 100644 index 9adaba4..0000000 --- a/.github/linters/tox.ini +++ /dev/null @@ -1,36 +0,0 @@ -[flake8] -ignore = - # module level import not at top of file - E402, - # line break after binary operator - W504, - # line break before binary operator - W503, - # E731: Do not assign a lambda expression, use a def - E731 - -exclude = - # No need to traverse our git directory - .git, - build, - dist, - .eggs, - *.egg-info, - examples, - docker_files, - .github, - .local, - docs, - Samples, - __pycache__, - .venv*, - .env*, - .vscode, - venv*, - scripts - -max-line-length = 80 - -per-file-ignores = - # import not used - __init__.py:F401 \ No newline at end of file diff --git a/.github/workflows/linting_extension_base.yml b/.github/workflows/linting_extension_base.yml deleted file mode 100644 index 830d490..0000000 --- a/.github/workflows/linting_extension_base.yml +++ /dev/null @@ -1,80 +0,0 @@ ---- -########################### -########################### -## Linter GitHub Actions ## -########################### -########################### - -name: Lint azurefunctions-extensions-base - -# -# Documentation: -# https://help.github.com/en/articles/workflow-syntax-for-github-actions -# - -############################# -# Start the job on all push # -############################# -on: - workflow_dispatch: - push: - branches: - - dev - - main - - 'release/*' - paths: - - 'azurefunctions-extensions-base/**' - pull_request: - branches: - - dev - - main - - 'release/*' - paths: - - 'azurefunctions-extensions-base/**' - -############### -# Set the Job # -############### -jobs: - build: - # Name the Job - name: Lint azurefunctions-extensions-base - # Set the agent to run on - runs-on: ubuntu-latest - permissions: - actions: read - checks: read - contents: read - deployments: read - issues: read - packages: read - pull-requests: read - repository-projects: read - security-events: read - statuses: write - - ################## - # Load all steps # - ################## - steps: - ########################## - # Checkout the code base # - ########################## - - name: Checkout Code - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - ################################ - # Run Linter against code base # - ################################ - - name: Lint Code Base - uses: super-linter/super-linter@v6.3.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VALIDATE_PYTHON: true - VALIDATE_PYTHON_BLACK: true # same as above - VALIDATE_PYTHON_ISORT: true # same as above - PYTHON_ISORT_CONFIG_FILE: .github/linters/.isort.cfg - FILTER_REGEX_INCLUDE: azurefunctions-extensions-base/.* - DEFAULT_BRANCH: origin/dev diff --git a/.github/workflows/linting_extension_blob.yml b/.github/workflows/linting_extension_blob.yml deleted file mode 100644 index 0dcc676..0000000 --- a/.github/workflows/linting_extension_blob.yml +++ /dev/null @@ -1,80 +0,0 @@ ---- -########################### -########################### -## Linter GitHub Actions ## -########################### -########################### - -name: Lint azurefunctions-extensions-bindings-blob - -# -# Documentation: -# https://help.github.com/en/articles/workflow-syntax-for-github-actions -# - -############################# -# Start the job on all push # -############################# -on: - workflow_dispatch: - push: - branches: - - dev - - main - - 'release/*' - paths: - - 'azurefunctions-extensions-bindings-blob/**' - pull_request: - branches: - - dev - - main - - 'release/*' - paths: - - 'azurefunctions-extensions-bindings-blob/**' - -############### -# Set the Job # -############### -jobs: - build: - # Name the Job - name: Lint azurefunctions-extensions-bindings-blob - # Set the agent to run on - runs-on: ubuntu-latest - permissions: - actions: read - checks: read - contents: read - deployments: read - issues: read - packages: read - pull-requests: read - repository-projects: read - security-events: read - statuses: write - - ################## - # Load all steps # - ################## - steps: - ########################## - # Checkout the code base # - ########################## - - name: Checkout Code - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - ################################ - # Run Linter against code base # - ################################ - - name: Lint Code Base - uses: super-linter/super-linter@v6.3.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VALIDATE_PYTHON: true - VALIDATE_PYTHON_BLACK: true # same as above -# VALIDATE_PYTHON_ISORT: true # same as above TODO: fix conflicting linters - PYTHON_ISORT_CONFIG_FILE: .github/linters/.isort.cfg - FILTER_REGEX_INCLUDE: azurefunctions-extensions-bindings-blob/* - DEFAULT_BRANCH: origin/dev diff --git a/.github/workflows/linting_extension_fastapi.yml b/.github/workflows/linting_extension_fastapi.yml deleted file mode 100644 index 61c0098..0000000 --- a/.github/workflows/linting_extension_fastapi.yml +++ /dev/null @@ -1,80 +0,0 @@ ---- -########################### -########################### -## Linter GitHub Actions ## -########################### -########################### - -name: Lint azurefunctions-extensions-http-fastapi - -# -# Documentation: -# https://help.github.com/en/articles/workflow-syntax-for-github-actions -# - -############################# -# Start the job on all push # -############################# -on: - workflow_dispatch: - push: - branches: - - dev - - main - - 'release/*' - paths: - - 'azurefunctions-extensions-http-fastapi/**' - pull_request: - branches: - - dev - - main - - 'release/*' - paths: - - 'azurefunctions-extensions-http-fastapi/**' - -############### -# Set the Job # -############### -jobs: - build: - # Name the Job - name: Lint azurefunctions-extensions-http-fastapi - # Set the agent to run on - runs-on: ubuntu-latest - permissions: - actions: read - checks: read - contents: read - deployments: read - issues: read - packages: read - pull-requests: read - repository-projects: read - security-events: read - statuses: write - - ################## - # Load all steps # - ################## - steps: - ########################## - # Checkout the code base # - ########################## - - name: Checkout Code - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - ################################ - # Run Linter against code base # - ################################ - - name: Lint Code Base - uses: super-linter/super-linter@v6.3.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VALIDATE_PYTHON: true - VALIDATE_PYTHON_BLACK: true # same as above - VALIDATE_PYTHON_ISORT: true # same as above - PYTHON_ISORT_CONFIG_FILE: .github/linters/.isort.cfg - FILTER_REGEX_INCLUDE: azurefunctions-extensions-http-fastapi/* - DEFAULT_BRANCH: origin/dev diff --git a/.github/workflows/pr_title_enforcer.yml b/.github/workflows/pr_title_enforcer.yml deleted file mode 100644 index f9f6c91..0000000 --- a/.github/workflows/pr_title_enforcer.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: "PR Title Enforcer" - -on: - pull_request: - types: - - opened - - edited - - synchronize - -permissions: - pull-requests: read - -jobs: - main: - name: Validate PR title - runs-on: ubuntu-latest - steps: - - uses: amannn/action-semantic-pull-request@v5 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/azurefunctions-extensions-base/azurefunctions/extensions/base/utils.py b/azurefunctions-extensions-base/azurefunctions/extensions/base/utils.py index 1169b01..05f2e90 100644 --- a/azurefunctions-extensions-base/azurefunctions/extensions/base/utils.py +++ b/azurefunctions-extensions-base/azurefunctions/extensions/base/utils.py @@ -6,7 +6,7 @@ import re from abc import ABC from enum import Enum -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable, Optional from . import meta diff --git a/azurefunctions-extensions-base/pyproject.toml b/azurefunctions-extensions-base/pyproject.toml index 8554675..2944ae0 100644 --- a/azurefunctions-extensions-base/pyproject.toml +++ b/azurefunctions-extensions-base/pyproject.toml @@ -27,11 +27,13 @@ classifiers= [ [project.optional-dependencies] dev = [ - 'pytest', - 'pytest-cov', - 'coverage', - 'pytest-instafail', - 'pre-commit' + 'flake8', + 'mypy', + 'pytest', + 'pytest-cov', + 'coverage', + 'pytest-instafail', + 'pre-commit' ] [tool.setuptools.dynamic] diff --git a/azurefunctions-extensions-base/tests/test_code_quality.py b/azurefunctions-extensions-base/tests/test_code_quality.py new file mode 100644 index 0000000..3ea4918 --- /dev/null +++ b/azurefunctions-extensions-base/tests/test_code_quality.py @@ -0,0 +1,51 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import pathlib +import subprocess +import sys +import unittest + +ROOT_PATH = pathlib.Path(__file__).parent.parent.parent + + +class TestCodeQuality(unittest.TestCase): + def test_mypy(self): + try: + import mypy # NoQA + except ImportError as e: + raise unittest.SkipTest('mypy module is missing') from e + + try: + subprocess.run( + [sys.executable, '-m', 'mypy', '-m', 'azurefunctions-extensions-base'], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=str(ROOT_PATH)) + except subprocess.CalledProcessError as ex: + output = ex.output.decode() + raise AssertionError( + f'mypy validation failed:\n{output}') from None + + def test_flake8(self): + try: + import flake8 # NoQA + except ImportError as e: + raise unittest.SkipTest('flake8 module is missing') from e + + config_path = ROOT_PATH / '.flake8' + if not config_path.exists(): + raise unittest.SkipTest('could not locate the .flake8 file') + + try: + subprocess.run( + [sys.executable, '-m', 'flake8', 'azurefunctions-extensions-base', + '--config', str(config_path)], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=str(ROOT_PATH)) + except subprocess.CalledProcessError as ex: + output = ex.output.decode() + raise AssertionError( + f'flake8 validation failed:\n{output}') from None diff --git a/azurefunctions-extensions-bindings-blob/azurefunctions/extensions/bindings/blob/blobClient.py b/azurefunctions-extensions-bindings-blob/azurefunctions/extensions/bindings/blob/blobClient.py index 269e278..bec7403 100644 --- a/azurefunctions-extensions-bindings-blob/azurefunctions/extensions/bindings/blob/blobClient.py +++ b/azurefunctions-extensions-bindings-blob/azurefunctions/extensions/bindings/blob/blobClient.py @@ -39,7 +39,8 @@ def get_sdk_type(self): through a BlobServiceClient. There are two ways to create a BlobServiceClient: 1. Through the constructor: this is the only option when using Managed Identity - 2. Through from_connection_string: this is the only option when not using Managed Identity + 2. Through from_connection_string: this is the only option when + not using Managed Identity We track if Managed Identity is being used through a flag. """ diff --git a/azurefunctions-extensions-bindings-blob/azurefunctions/extensions/bindings/blob/utils.py b/azurefunctions-extensions-bindings-blob/azurefunctions/extensions/bindings/blob/utils.py index a829fdf..5421061 100644 --- a/azurefunctions-extensions-bindings-blob/azurefunctions/extensions/bindings/blob/utils.py +++ b/azurefunctions-extensions-bindings-blob/azurefunctions/extensions/bindings/blob/utils.py @@ -8,7 +8,8 @@ def get_connection_string(connection_string: str) -> str: Validates and returns the connection string. If the connection string is not an App Setting, an error will be thrown. - When using managed identity, the connection string variable name is formatted like so: + When using managed identity, the connection string variable name is formatted + like so: Input: __serviceUri Trigger: __blobServiceUri The variable received will be . Therefore, we need to append diff --git a/azurefunctions-extensions-bindings-blob/pyproject.toml b/azurefunctions-extensions-bindings-blob/pyproject.toml index b2b919b..da90630 100644 --- a/azurefunctions-extensions-bindings-blob/pyproject.toml +++ b/azurefunctions-extensions-bindings-blob/pyproject.toml @@ -31,6 +31,8 @@ dependencies = [ [project.optional-dependencies] dev = [ + 'flake8', + 'mypy', 'pytest', 'pytest-cov', 'coverage', diff --git a/azurefunctions-extensions-bindings-blob/tests/test_blobclient.py b/azurefunctions-extensions-bindings-blob/tests/test_blobclient.py index 8d087b6..e7c7e32 100644 --- a/azurefunctions-extensions-bindings-blob/tests/test_blobclient.py +++ b/azurefunctions-extensions-bindings-blob/tests/test_blobclient.py @@ -142,13 +142,13 @@ def test_invalid_input_populated(self): with self.assertRaises(ValueError) as e: datum: Datum = Datum(value=sample_mbd, type="model_binding_data") - result: BlobClient = BlobClientConverter.decode( + _: BlobClient = BlobClientConverter.decode( data=datum, trigger_metadata=None, pytype=BlobClient ) self.assertEqual( e.exception.args[0], - "Storage account connection string NotARealConnectionString does not exist. " - "Please make sure that it is a defined App Setting.", + "Storage account connection string NotARealConnectionString" + "does not exist. Please make sure that it is a defined App Setting.", ) def test_none_input_populated(self): @@ -167,12 +167,13 @@ def test_none_input_populated(self): with self.assertRaises(ValueError) as e: datum: Datum = Datum(value=sample_mbd, type="model_binding_data") - result: BlobClient = BlobClientConverter.decode( + _: BlobClient = BlobClientConverter.decode( data=datum, trigger_metadata=None, pytype=BlobClient ) self.assertEqual( e.exception.args[0], - "Storage account connection string cannot be None. Please provide a connection string.", + "Storage account connection string cannot be None." + " Please provide a connection string.", ) def test_input_populated_managed_identity_input(self): diff --git a/azurefunctions-extensions-bindings-blob/tests/test_code_quality.py b/azurefunctions-extensions-bindings-blob/tests/test_code_quality.py new file mode 100644 index 0000000..63c90d7 --- /dev/null +++ b/azurefunctions-extensions-bindings-blob/tests/test_code_quality.py @@ -0,0 +1,53 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import pathlib +import subprocess +import sys +import unittest + +ROOT_PATH = pathlib.Path(__file__).parent.parent.parent + + +class TestCodeQuality(unittest.TestCase): + def test_mypy(self): + try: + import mypy # NoQA + except ImportError as e: + raise unittest.SkipTest('mypy module is missing') from e + + try: + subprocess.run( + [sys.executable, '-m', 'mypy', '-m', + 'azurefunctions-extensions-bindings-blob'], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=str(ROOT_PATH)) + except subprocess.CalledProcessError as ex: + output = ex.output.decode() + raise AssertionError( + f'mypy validation failed:\n{output}') from None + + def test_flake8(self): + try: + import flake8 # NoQA + except ImportError as e: + raise unittest.SkipTest('flake8 module is missing') from e + + config_path = ROOT_PATH / '.flake8' + if not config_path.exists(): + raise unittest.SkipTest('could not locate the .flake8 file') + + try: + subprocess.run( + [sys.executable, '-m', 'flake8', + 'azurefunctions-extensions-bindings-blob', + '--config', str(config_path)], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=str(ROOT_PATH)) + except subprocess.CalledProcessError as ex: + output = ex.output.decode() + raise AssertionError( + f'flake8 validation failed:\n{output}') from None diff --git a/azurefunctions-extensions-bindings-blob/tests/test_containerclient.py b/azurefunctions-extensions-bindings-blob/tests/test_containerclient.py index b624743..ad9d801 100644 --- a/azurefunctions-extensions-bindings-blob/tests/test_containerclient.py +++ b/azurefunctions-extensions-bindings-blob/tests/test_containerclient.py @@ -140,13 +140,13 @@ def test_invalid_input_populated(self): with self.assertRaises(ValueError) as e: datum: Datum = Datum(value=sample_mbd, type="model_binding_data") - result: ContainerClient = BlobClientConverter.decode( + _: ContainerClient = BlobClientConverter.decode( data=datum, trigger_metadata=None, pytype=ContainerClient ) self.assertEqual( e.exception.args[0], - "Storage account connection string NotARealConnectionString does not exist. " - "Please make sure that it is a defined App Setting.", + "Storage account connection string NotARealConnectionString" + " does not exist. Please make sure that it is a defined App Setting.", ) def test_none_input_populated(self): @@ -165,12 +165,13 @@ def test_none_input_populated(self): with self.assertRaises(ValueError) as e: datum: Datum = Datum(value=sample_mbd, type="model_binding_data") - result: ContainerClient = BlobClientConverter.decode( + _: ContainerClient = BlobClientConverter.decode( data=datum, trigger_metadata=None, pytype=ContainerClient ) self.assertEqual( e.exception.args[0], - "Storage account connection string cannot be None. Please provide a connection string.", + "Storage account connection string cannot be None." + " Please provide a connection string.", ) def test_input_populated_managed_identity_input(self): diff --git a/azurefunctions-extensions-bindings-blob/tests/test_ssd.py b/azurefunctions-extensions-bindings-blob/tests/test_ssd.py index 17c86e2..fbd16bc 100644 --- a/azurefunctions-extensions-bindings-blob/tests/test_ssd.py +++ b/azurefunctions-extensions-bindings-blob/tests/test_ssd.py @@ -145,13 +145,13 @@ def test_invalid_input_populated(self): with self.assertRaises(ValueError) as e: datum: Datum = Datum(value=sample_mbd, type="model_binding_data") - result: StorageStreamDownloader = BlobClientConverter.decode( + _: StorageStreamDownloader = BlobClientConverter.decode( data=datum, trigger_metadata=None, pytype=StorageStreamDownloader ) self.assertEqual( e.exception.args[0], - "Storage account connection string NotARealConnectionString does not exist. " - "Please make sure that it is a defined App Setting.", + "Storage account connection string NotARealConnectionString" + " does not exist. Please make sure that it is a defined App Setting.", ) def test_none_input_populated(self): @@ -170,12 +170,13 @@ def test_none_input_populated(self): with self.assertRaises(ValueError) as e: datum: Datum = Datum(value=sample_mbd, type="model_binding_data") - result: StorageStreamDownloader = BlobClientConverter.decode( + _: StorageStreamDownloader = BlobClientConverter.decode( data=datum, trigger_metadata=None, pytype=StorageStreamDownloader ) self.assertEqual( e.exception.args[0], - "Storage account connection string cannot be None. Please provide a connection string.", + "Storage account connection string cannot be None." + " Please provide a connection string.", ) def test_input_populated_managed_identity_input(self): diff --git a/azurefunctions-extensions-http-fastapi/pyproject.toml b/azurefunctions-extensions-http-fastapi/pyproject.toml index 96581ab..19b0e08 100644 --- a/azurefunctions-extensions-http-fastapi/pyproject.toml +++ b/azurefunctions-extensions-http-fastapi/pyproject.toml @@ -33,6 +33,8 @@ dependencies = [ [project.optional-dependencies] dev = [ + 'flake8', + 'mypy', 'pytest', 'pytest-cov', 'coverage', diff --git a/azurefunctions-extensions-http-fastapi/tests/test_code_quality.py b/azurefunctions-extensions-http-fastapi/tests/test_code_quality.py new file mode 100644 index 0000000..1c3863a --- /dev/null +++ b/azurefunctions-extensions-http-fastapi/tests/test_code_quality.py @@ -0,0 +1,53 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import pathlib +import subprocess +import sys +import unittest + +ROOT_PATH = pathlib.Path(__file__).parent.parent.parent + + +class TestCodeQuality(unittest.TestCase): + def test_mypy(self): + try: + import mypy # NoQA + except ImportError as e: + raise unittest.SkipTest('mypy module is missing') from e + + try: + subprocess.run( + [sys.executable, '-m', 'mypy', '-m', + 'azurefunctions-extensions-http-fastapi'], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=str(ROOT_PATH)) + except subprocess.CalledProcessError as ex: + output = ex.output.decode() + raise AssertionError( + f'mypy validation failed:\n{output}') from None + + def test_flake8(self): + try: + import flake8 # NoQA + except ImportError as e: + raise unittest.SkipTest('flake8 module is missing') from e + + config_path = ROOT_PATH / '.flake8' + if not config_path.exists(): + raise unittest.SkipTest('could not locate the .flake8 file') + + try: + subprocess.run( + [sys.executable, '-m', 'flake8', + 'azurefunctions-extensions-http-fastapi', + '--config', str(config_path)], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=str(ROOT_PATH)) + except subprocess.CalledProcessError as ex: + output = ex.output.decode() + raise AssertionError( + f'flake8 validation failed:\n{output}') from None diff --git a/azurefunctions-extensions-http-fastapi/tests/test_web.py b/azurefunctions-extensions-http-fastapi/tests/test_web.py index 6400726..9491f0c 100644 --- a/azurefunctions-extensions-http-fastapi/tests/test_web.py +++ b/azurefunctions-extensions-http-fastapi/tests/test_web.py @@ -154,7 +154,8 @@ def test_sync_route_params(self): # Call the sync_route_params method with the mock request and path parameters synchronizer.sync_route_params(mock_request, path_params) - # Assert that the request's path_params have been updated with the provided path parameters + # Assert that the request's path_params have been updated with the + # provided path parameters mock_request.path_params.clear.assert_called_once() mock_request.path_params.update.assert_called_once_with(path_params) @@ -176,15 +177,14 @@ def test_sync_route_params_missing_path_params(self): # Create an instance of the ConcreteRequestSynchronizer synchronizer = RequestSynchronizer() - # Call the sync_route_params method with the mock request and None path parameters + # Call the sync_route_params method with the mock request + # and None path parameters with self.assertRaises(TypeError): synchronizer.sync_route_params(mock_request, None) class TestExtensionClasses(unittest.TestCase): def test_request(self): - from azurefunctions.extensions.http.fastapi.web import Request - self.assertEqual(RequestTrackerMeta.get_request_type(), FastApiRequest) self.assertTrue( isinstance(RequestTrackerMeta.get_synchronizer(), RequestSynchronizer) From b831fce99a336b268288a60987a1a7acccfff30c Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Thu, 20 Mar 2025 19:08:18 -0500 Subject: [PATCH 09/10] fix blob test --- .../tests/test_blobclient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurefunctions-extensions-bindings-blob/tests/test_blobclient.py b/azurefunctions-extensions-bindings-blob/tests/test_blobclient.py index e7c7e32..ca3782d 100644 --- a/azurefunctions-extensions-bindings-blob/tests/test_blobclient.py +++ b/azurefunctions-extensions-bindings-blob/tests/test_blobclient.py @@ -148,7 +148,7 @@ def test_invalid_input_populated(self): self.assertEqual( e.exception.args[0], "Storage account connection string NotARealConnectionString" - "does not exist. Please make sure that it is a defined App Setting.", + " does not exist. Please make sure that it is a defined App Setting.", ) def test_none_input_populated(self): From 2838dc19d340de35a5d62442f6b9d44e17dbbdcd Mon Sep 17 00:00:00 2001 From: hallvictoria Date: Mon, 24 Mar 2025 10:33:02 -0500 Subject: [PATCH 10/10] gh issue template & pr title check --- .github/ISSUE_TEMPLATE/bug_report.md | 28 +++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++++ .github/dependabot.yml | 33 +++++++++++++++++++++++ .github/workflows/pr_title_enforcer.yml | 20 ++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/pr_title_enforcer.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..8bcefe3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' +--- + +- **Package Name**: +- **Package Version**: +- **Operating System**: +- **Python Version**: + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..24473de --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..7f66571 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,33 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "pip" + # Files stored in repository root + directory: "/azurefunctions-extensions-base" + schedule: + interval: "weekly" + commit-message: + # Prefix all commit messages with "build: " + prefix: "build" + + - package-ecosystem: "pip" + # Files stored in `app` directory + directory: "/azurefunctions-extensions-http-fastapi" + schedule: + interval: "weekly" + commit-message: + # Prefix all commit messages with "build: " + prefix: "build" + + - package-ecosystem: "pip" + # Files stored in `app` directory + directory: "/azurefunctions-extensions-bindings-blob" + schedule: + interval: "weekly" + commit-message: + # Prefix all commit messages with "build: " + prefix: "build" diff --git a/.github/workflows/pr_title_enforcer.yml b/.github/workflows/pr_title_enforcer.yml new file mode 100644 index 0000000..f9f6c91 --- /dev/null +++ b/.github/workflows/pr_title_enforcer.yml @@ -0,0 +1,20 @@ +name: "PR Title Enforcer" + +on: + pull_request: + types: + - opened + - edited + - synchronize + +permissions: + pull-requests: read + +jobs: + main: + name: Validate PR title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}