Skip to content

Commit 2cd34a0

Browse files
authored
Merge branch 'main' into copilot/fix-lro-poller-transaction-status
2 parents 304414a + da39271 commit 2cd34a0

114 files changed

Lines changed: 2424 additions & 6357 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.

.github/workflows/post-apiview.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ jobs:
1818
"JobId=azsdk-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}",
1919
]
2020
if: |
21-
toJson(github.event.check_run.pull_requests) != '[]' &&
2221
github.event.check_run.check_suite.app.name == 'Azure Pipelines' && (
2322
contains(github.event.check_run.name, 'Build Build') ||
2423
contains(github.event.check_run.name, 'Build Analyze') ||

doc/sdk_and_ai.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Using the Azure SDK for Python with AI tools
2+
3+
AI-powered coding tools can help you write, understand, and debug applications that use the Azure SDK.
4+
This page lists common options and integrations available today.
5+
6+
This page can be linked by [aka.ms/azsdk/python/ai](https://aka.ms/azsdk/python/ai)
7+
8+
## AI coding tools
9+
10+
Several tools support AI-assisted development with the Azure SDK:
11+
12+
| Tool | Description |
13+
|---|---|
14+
| [VS Code](https://code.visualstudio.com/) | Code editor with built-in AI features and Copilot integration |
15+
| [GitHub Copilot](https://github.com/features/copilot) | AI code completion and chat inside VS Code, Visual Studio, JetBrains IDEs, and GitHub.com |
16+
| [Copilot CLI](https://docs.github.com/en/copilot/github-copilot-in-the-cli/about-github-copilot-in-the-cli) | AI assistance for shell commands and Azure CLI usage |
17+
| [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | Terminal-based AI coding agent from Anthropic |
18+
| [Cursor](https://www.cursor.com/) | AI-first code editor with chat and inline editing |
19+
| [Aider](https://aider.chat/) | Terminal-based AI pair programming tool |
20+
21+
This is not an exhaustive list — most AI coding tools that support chat or code generation can work with the Azure SDK.
22+
23+
## Azure MCP Server
24+
25+
The [Azure MCP Server](https://learn.microsoft.com/azure/developer/azure-mcp-server/get-started) exposes Azure resource operations to AI tools through the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/).
26+
This lets an AI coding agent query, create, and manage Azure resources directly during a conversation.
27+
28+
See the [getting started guide](https://learn.microsoft.com/azure/developer/azure-mcp-server/get-started) for setup instructions.
29+
30+
## Azure SDK skills
31+
32+
The Microsoft skills marketplace provides Azure SDK skills that give AI agents context about Azure SDK conventions, code generation, and package management. These skills work with CLI-based AI tools that support plugins, such as Claude Code and Copilot CLI.
33+
34+
Install the Microsoft skills marketplace:
35+
36+
```
37+
/plugin marketplace add Microsoft/skills
38+
```
39+
40+
Install the Azure SDK skills plugin:
41+
42+
```
43+
/plugin install azure-sdk-python@skills
44+
```
45+
46+
Verify installation:
47+
48+
```
49+
/plugin list
50+
```
51+
52+
Update the plugin:
53+
54+
```
55+
/plugin update azure-sdk-python@skills
56+
```
57+
58+
Skills provide domain-specific context that helps AI tools generate more accurate SDK code.
59+
60+
For operational Azure tasks (managing resources, querying services), see the [Azure skills](https://github.com/microsoft/azure-skills) repository.
61+
62+
## Further reading
63+
64+
- [Azure SDK for Python documentation](https://learn.microsoft.com/azure/developer/python/sdk/azure-sdk-overview)
65+
- [Azure SDK design guidelines](https://azure.github.io/azure-sdk/python_design.html)
66+
- [Azure MCP Server](https://learn.microsoft.com/azure/developer/azure-mcp-server/get-started)
67+
68+
## Feedback
69+
70+
If you have feedback on your AI experience with the Azure SDK for Python, [open an issue](https://github.com/Azure/azure-sdk-for-python/issues/new/choose) on the repository.

eng/common/pipelines/templates/steps/create-tags-and-git-release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ steps:
2525
timeoutInMinutes: 5
2626
env:
2727
GH_TOKEN: $(azuresdk-github-pat)
28+
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
2829
${{ if ne(parameters.NpmConfigUserConfig, '') }}:
2930
NPM_CONFIG_USERCONFIG: ${{ parameters.NpmConfigUserConfig }}
3031
${{ if ne(parameters.NpmConfigRegistry, '') }}:

eng/common/pipelines/templates/steps/publish-blobs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ steps:
2626
pwsh: true
2727
env:
2828
AZCOPY_AUTO_LOGIN_TYPE: 'PSCRED'
29+
SYSTEM_ACCESSTOKEN: $(System.AccessToken)

eng/pipelines/templates/jobs/ci.yml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,6 @@ parameters:
6464
- name: EnvVars
6565
type: object
6666
default: {}
67-
- name: PythonVersionForAnalyze
68-
type: string
69-
default: ''
7067

7168
jobs:
7269
- job: 'Build_Linux'
@@ -212,10 +209,10 @@ jobs:
212209

213210
steps:
214211
- task: UsePythonVersion@0
215-
displayName: "Use Python ${{ coalesce(parameters.PythonVersionForAnalyze, '$(PythonVersion)') }}"
212+
displayName: "Use Python $(PythonVersion)"
216213
condition: succeededOrFailed()
217214
inputs:
218-
versionSpec: ${{ coalesce(parameters.PythonVersionForAnalyze, '$(PythonVersion)') }}
215+
versionSpec: $(PythonVersion)
219216
- template: /eng/pipelines/templates/steps/use-venv.yml
220217

221218
- template: /eng/common/pipelines/templates/steps/check-spelling.yml

eng/pipelines/templates/stages/archetype-sdk-client.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,6 @@ parameters:
8484
- name: EnvVars
8585
type: object
8686
default: {}
87-
- name: PythonVersionForAnalyze
88-
type: string
89-
default: ''
9087

9188
extends:
9289
template: /eng/pipelines/templates/stages/1es-redirect.yml
@@ -123,7 +120,6 @@ extends:
123120
VerifyAutorest: ${{ parameters.VerifyAutorest }}
124121
TestProxy: ${{ parameters.TestProxy }}
125122
GenerateApiReviewForManualOnly: ${{ parameters.GenerateApiReviewForManualOnly }}
126-
PythonVersionForAnalyze: ${{ parameters.PythonVersionForAnalyze }}
127123

128124
variables:
129125
- template: /eng/pipelines/templates/variables/globals.yml

eng/scripts/dispatch_checks.py

Lines changed: 36 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import argparse
22
import asyncio
3+
import glob
34
import os
45
import sys
56
import time
@@ -15,6 +16,7 @@
1516
from ci_tools.scenario.generation import build_whl_for_req, replace_dev_reqs
1617
from ci_tools.logging import configure_logging, logger
1718
from ci_tools.environment_exclusions import is_check_enabled, CHECK_DEFAULTS
19+
from ci_tools.parsing import get_config_setting
1820
from devtools_testutils.proxy_startup import prepare_local_tool
1921
from packaging.requirements import Requirement
2022

@@ -49,6 +51,7 @@ class ProxyProcess:
4951
"sdist",
5052
"devtest",
5153
"optional",
54+
"import_all",
5255
"latestdependency",
5356
"mindependency",
5457
}
@@ -84,9 +87,7 @@ def _compare_req_to_injected_reqs(parsed_req, injected_packages: List[str]) -> b
8487
return any(parsed_req.name in req for req in injected_packages)
8588

8689

87-
def _inject_custom_reqs(
88-
req_file: str, injected_packages: str, package_dir: str
89-
) -> None:
90+
def _inject_custom_reqs(req_file: str, injected_packages: str, package_dir: str) -> None:
9091
req_lines = []
9192
injected_list = [p for p in re.split(r"[\s,]", injected_packages) if p]
9293

@@ -115,8 +116,7 @@ def _inject_custom_reqs(
115116
all_adjustments = installable + [
116117
line_tuple[0].strip()
117118
for line_tuple in req_lines
118-
if line_tuple[0].strip()
119-
and not _compare_req_to_injected_reqs(line_tuple[1], all_filter_names)
119+
if line_tuple[0].strip() and not _compare_req_to_injected_reqs(line_tuple[1], all_filter_names)
120120
]
121121
else:
122122
all_adjustments = installable
@@ -138,6 +138,7 @@ async def run_check(
138138
mark_arg: Optional[str],
139139
dest_dir: Optional[str] = None,
140140
service: Optional[str] = None,
141+
python_version: Optional[str] = None,
141142
) -> CheckResult:
142143
"""Run a single check (subprocess) within a concurrency semaphore, capturing output and timing.
143144
@@ -161,6 +162,8 @@ async def run_check(
161162
async with semaphore:
162163
start = time.time()
163164
cmd = base_args + [check, "--isolate", package]
165+
if python_version:
166+
cmd += ["--python", python_version]
164167
if service:
165168
cmd += ["--service", service]
166169
if mark_arg:
@@ -172,9 +175,7 @@ async def run_check(
172175
env["PROXY_URL"] = f"http://localhost:{proxy_port}"
173176

174177
if in_ci():
175-
env["PROXY_ASSETS_FOLDER"] = os.path.join(
176-
root_dir, ".assets_distributed", str(proxy_port)
177-
)
178+
env["PROXY_ASSETS_FOLDER"] = os.path.join(root_dir, ".assets_distributed", str(proxy_port))
178179
try:
179180
logger.info(" ".join(cmd))
180181
proc = await asyncio.create_subprocess_exec(
@@ -194,9 +195,7 @@ async def run_check(
194195
stderr = stderr_b.decode(errors="replace")
195196
exit_code = proc.returncode or 0
196197
status = "OK" if exit_code == 0 else f"FAIL({exit_code})"
197-
logger.info(
198-
f"[END {idx}/{total}] {check} :: {package} -> {status} in {duration:.2f}s"
199-
)
198+
logger.info(f"[END {idx}/{total}] {check} :: {package} -> {status} in {duration:.2f}s")
200199
# Print captured output after completion to avoid interleaving
201200
header = f"===== OUTPUT: {check} :: {package} (exit {exit_code}) ====="
202201
trailer = "=" * len(header)
@@ -220,10 +219,10 @@ async def run_check(
220219
# finally, we need to clean up any temp dirs created by --isolate
221220
if in_ci():
222221
package_name = os.path.basename(os.path.normpath(package))
223-
isolate_dir = os.path.join(
224-
root_dir, ".venv", package_name, f".venv_{check}"
225-
)
226-
ISOLATE_DIRS_TO_CLEAN.append(isolate_dir)
222+
venv_pkg_root = os.path.join(root_dir, ".venv", package_name)
223+
# match both .venv_{check} and version-qualified .venv_{check}_py311 etc.
224+
for d in glob.glob(os.path.join(venv_pkg_root, f".venv_{check}*")):
225+
ISOLATE_DIRS_TO_CLEAN.append(d)
227226
return CheckResult(package, check, exit_code, duration, stdout, stderr)
228227

229228

@@ -247,14 +246,10 @@ def summarize(results: List[CheckResult]) -> int:
247246
print("-" * len(header))
248247
for r in sorted(results, key=lambda x: (x.exit_code != 0, x.package, x.check)):
249248
status = "OK" if r.exit_code == 0 else f"FAIL({r.exit_code})"
250-
print(
251-
f"{r.package.ljust(pkg_w)} {r.check.ljust(chk_w)} {status.ljust(8)} {r.duration:>10.2f}"
252-
)
249+
print(f"{r.package.ljust(pkg_w)} {r.check.ljust(chk_w)} {status.ljust(8)} {r.duration:>10.2f}")
253250
worst = max((r.exit_code for r in results), default=0)
254251
failed = [r for r in results if r.exit_code != 0]
255-
print(
256-
f"\nTotal checks: {len(results)} | Failed: {len(failed)} | Worst exit code: {worst}"
257-
)
252+
print(f"\nTotal checks: {len(results)} | Failed: {len(failed)} | Worst exit code: {worst}")
258253
return worst
259254

260255

@@ -292,14 +287,10 @@ async def run_all_checks(
292287
dependency_tools_path = os.path.join(root_dir, "eng", "dependency_tools.txt")
293288

294289
if in_ci():
295-
logger.info(
296-
"Replacing relative requirements in eng/test_tools.txt with prebuilt wheels."
297-
)
290+
logger.info("Replacing relative requirements in eng/test_tools.txt with prebuilt wheels.")
298291
replace_dev_reqs(test_tools_path, root_dir, wheel_dir)
299292

300-
logger.info(
301-
"Replacing relative requirements in eng/dependency_tools.txt with prebuilt wheels."
302-
)
293+
logger.info("Replacing relative requirements in eng/dependency_tools.txt with prebuilt wheels.")
303294
replace_dev_reqs(dependency_tools_path, root_dir, wheel_dir)
304295

305296
for pkg in packages:
@@ -321,15 +312,19 @@ async def run_all_checks(
321312
if not is_check_enabled(package, check, CHECK_DEFAULTS.get(check, True)):
322313
logger.warning(f"Skipping disabled check {check} for package {package}")
323314
continue
324-
logger.info(
325-
f"Assigning proxy port {next_proxy_port} to check {check} for package {package}"
326-
)
327-
scheduled.append((package, check, next_proxy_port))
315+
logger.info(f"Assigning proxy port {next_proxy_port} to check {check} for package {package}")
316+
317+
# Check if this package overrides the Python version for analysis
318+
pkg_python_version = get_config_setting(package, "analyze_python_version", None)
319+
if pkg_python_version:
320+
logger.info(f"Package {package} overrides analyze Python version to {pkg_python_version}")
321+
322+
scheduled.append((package, check, next_proxy_port, pkg_python_version))
328323
next_proxy_port += 1
329324

330325
total = len(scheduled)
331326

332-
for idx, (package, check, proxy_port) in enumerate(scheduled, start=1):
327+
for idx, (package, check, proxy_port, pkg_python_version) in enumerate(scheduled, start=1):
333328
tasks.append(
334329
asyncio.create_task(
335330
run_check(
@@ -343,6 +338,7 @@ async def run_all_checks(
343338
mark_arg,
344339
dest_dir,
345340
service,
341+
pkg_python_version,
346342
)
347343
)
348344
)
@@ -358,15 +354,13 @@ async def run_all_checks(
358354
raise
359355
# Normalize exceptions
360356
norm_results: List[CheckResult] = []
361-
for res, (package, check, _) in zip(results, scheduled):
357+
for res, (package, check, _, _) in zip(results, scheduled):
362358
if isinstance(res, CheckResult):
363359
norm_results.append(res)
364360
elif isinstance(res, Exception):
365361
norm_results.append(CheckResult(package, check, 99, 0.0, "", str(res)))
366362
else:
367-
norm_results.append(
368-
CheckResult(package, check, 98, 0.0, "", f"Unknown result type: {res}")
369-
)
363+
norm_results.append(CheckResult(package, check, 98, 0.0, "", f"Unknown result type: {res}"))
370364
return summarize(norm_results)
371365

372366

@@ -442,15 +436,11 @@ def handler(signum, frame):
442436
),
443437
)
444438

445-
parser.add_argument(
446-
"--disablecov", help=("Flag. Disables code coverage."), action="store_true"
447-
)
439+
parser.add_argument("--disablecov", help=("Flag. Disables code coverage."), action="store_true")
448440

449441
parser.add_argument(
450442
"--service",
451-
help=(
452-
"Name of service directory (under sdk/) to test. Example: --service applicationinsights"
453-
),
443+
help=("Name of service directory (under sdk/) to test. Example: --service applicationinsights"),
454444
)
455445

456446
parser.add_argument(
@@ -517,9 +507,7 @@ def handler(signum, frame):
517507
else:
518508
target_dir = root_dir
519509

520-
logger.info(
521-
f"Beginning discovery for {args.service} and root dir {root_dir}. Resolving to {target_dir}."
522-
)
510+
logger.info(f"Beginning discovery for {args.service} and root dir {root_dir}. Resolving to {target_dir}.")
523511

524512
# ensure that recursive virtual envs aren't messed with by this call
525513
os.environ.pop("VIRTUAL_ENV", None)
@@ -536,9 +524,7 @@ def handler(signum, frame):
536524
)
537525

538526
if len(targeted_packages) == 0:
539-
logger.info(
540-
f"No packages collected for targeting string {args.glob_string} and root dir {root_dir}. Exit 0."
541-
)
527+
logger.info(f"No packages collected for targeting string {args.glob_string} and root dir {root_dir}. Exit 0.")
542528
exit(0)
543529

544530
logger.info(f"Executing checks with the executable {sys.executable}.")
@@ -570,9 +556,7 @@ def handler(signum, frame):
570556
try:
571557
proxy_executable = prepare_local_tool(root_dir)
572558
except Exception as exc:
573-
logger.error(
574-
f"Unable to prepare test proxy executable for recording restore: {exc}"
575-
)
559+
logger.error(f"Unable to prepare test proxy executable for recording restore: {exc}")
576560
sys.exit(1)
577561

578562
logger.info(
@@ -583,9 +567,7 @@ def handler(signum, frame):
583567
proxy_processes: List[ProxyProcess] = []
584568
try:
585569
if in_ci():
586-
logger.info(
587-
f"Ensuring {len(checks)} test proxies are running for requested checks..."
588-
)
570+
logger.info(f"Ensuring {len(checks)} test proxies are running for requested checks...")
589571
# Pass through service if set and not "auto"
590572
effective_service = args.service if (args.service and args.service != "auto") else None
591573
exit_code = asyncio.run(

0 commit comments

Comments
 (0)