From 0b266bf8d3060999f44c1956e51c87b0aa157696 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Thu, 26 Feb 2026 11:59:02 -0500 Subject: [PATCH 1/3] check changes --- .../emitter/src/code-model.ts | 2 +- .../http-client-python/emitter/src/utils.ts | 29 +++++++++---------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/http-client-python/emitter/src/code-model.ts b/packages/http-client-python/emitter/src/code-model.ts index 7a33b7b486a..1b015c1186f 100644 --- a/packages/http-client-python/emitter/src/code-model.ts +++ b/packages/http-client-python/emitter/src/code-model.ts @@ -263,7 +263,7 @@ function emitOperationGroups( // operation has same clientNamespace as the operation group for (const og of operationGroups) { for (const op of og.operations) { - op.clientNamespace = getClientNamespace(context, og.clientNamespace); + op.clientNamespace = og.clientNamespace; } } diff --git a/packages/http-client-python/emitter/src/utils.ts b/packages/http-client-python/emitter/src/utils.ts index 585ef6e9765..16042f71dd5 100644 --- a/packages/http-client-python/emitter/src/utils.ts +++ b/packages/http-client-python/emitter/src/utils.ts @@ -262,6 +262,7 @@ export function capitalize(name: string): string { return name[0].toUpperCase() + name.slice(1); } +// Library namespaces that should not be used as client namespaces const LIB_NAMESPACE = [ "azure.core", "azure.resourcemanager", @@ -272,29 +273,25 @@ const LIB_NAMESPACE = [ ]; export function getRootNamespace(context: PythonSdkContext): string { - let rootNamespace = ""; if (context.sdkPackage.clients.length > 0) { - rootNamespace = context.sdkPackage.clients[0].namespace; - } else if (context.sdkPackage.models.length > 0) { - const result = context.sdkPackage.models - .map((model) => model.namespace) - .filter((namespace) => !LIB_NAMESPACE.includes(namespace)); - if (result.length > 0) { - result.sort(); - rootNamespace = result[0]; - } + return context.sdkPackage.clients[0].namespace.toLowerCase(); } else if (context.sdkPackage.namespaces.length > 0) { - rootNamespace = context.sdkPackage.namespaces[0].fullName; + return context.sdkPackage.namespaces[0].fullName.toLowerCase(); } + return ""; +} - return rootNamespace.toLowerCase(); +function isLibraryNamespace(namespace: string): boolean { + const ns = namespace.toLowerCase(); + return LIB_NAMESPACE.some((lib) => ns.startsWith(lib)); } export function getClientNamespace(context: PythonSdkContext, clientNamespace: string) { - if ( - clientNamespace === "" || - LIB_NAMESPACE.some((item) => clientNamespace.toLowerCase().startsWith(item)) - ) { + // Namespace precedence: @clientNamespace > --namespace > original namespace + // These are resolved by TCGC and passed in as clientNamespace. + // However, models from library namespaces (azure.core, azure.resourcemanager, etc.) + // should use the SDK's root namespace instead. + if (clientNamespace === "" || isLibraryNamespace(clientNamespace)) { return getRootNamespace(context); } return clientNamespace.toLowerCase(); From 14233721db2e5ce1f329a5721d12924afcf98283 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Thu, 26 Feb 2026 14:17:03 -0500 Subject: [PATCH 2/3] add skill to diff changes with upstream --- .../.github/skills/diff-upstream/SKILL.md | 330 ++++++++++++++++++ .../skills/test-unbranded-emitter/SKILL.md | 175 ++++++++++ packages/http-client-python/.gitignore | 9 + 3 files changed, 514 insertions(+) create mode 100644 packages/http-client-python/.github/skills/diff-upstream/SKILL.md create mode 100644 packages/http-client-python/.github/skills/test-unbranded-emitter/SKILL.md create mode 100644 packages/http-client-python/.gitignore diff --git a/packages/http-client-python/.github/skills/diff-upstream/SKILL.md b/packages/http-client-python/.github/skills/diff-upstream/SKILL.md new file mode 100644 index 00000000000..983f4456576 --- /dev/null +++ b/packages/http-client-python/.github/skills/diff-upstream/SKILL.md @@ -0,0 +1,330 @@ +--- +name: diff-upstream +description: > + Regenerate the local emitter and diff the generated code against the upstream + baseline checked into autorest.python. Use this skill when the user wants to + see how their emitter changes affect generated code compared to what's currently + in production, says things like "diff upstream", "compare to baseline", "what + changed vs production", "show me the diff", "how does this affect generated code", + or wants to validate their changes produce the expected output differences. +--- + +# Diff Upstream Skill + +Regenerates local emitter output and compares it against the baseline generated +code checked into the autorest.python repository. Shows exactly what would change +if the current emitter changes were shipped. + +Optimized for local development with cached baseline and editor integration. + +## Paths + +All paths are relative to the http-client-python package root. + +| Item | Path | +|------|------| +| Package root | `~/Desktop/github/typespec/packages/http-client-python` | +| Local generated (unbranded) | `generator/test/unbranded/generated` | +| Local generated (azure) | `generator/test/azure/generated` | +| Baseline cache (unbranded) | `generator/test/unbranded/.baseline` (gitignored) | +| Baseline cache (azure) | `generator/test/azure/.baseline` (gitignored) | +| Diff summary output | `generator/test/.diff-summary.md` (gitignored) | +| Regenerate marker | `.last-regenerate` | +| Upstream repo | `https://github.com/Azure/autorest.python` | + +## Workflow + +### Step 1: Build and regenerate + +Always regenerate to see current changes: + +```bash +cd ~/Desktop/github/typespec/packages/http-client-python +npm run build +npm run regenerate +touch .last-regenerate +``` + +If build or regenerate fails, report errors and stop. + +### Step 2: Fetch or update baseline + +Fetch baseline files directly into the test folders using sparse checkout: + +```bash +cd ~/Desktop/github/typespec/packages/http-client-python + +BASELINE_UNBRANDED="generator/test/unbranded/.baseline" +BASELINE_AZURE="generator/test/azure/.baseline" +TEMP_CLONE="/tmp/autorest-python-baseline-$$" + +# Check if we need to fetch/update +if [ ! -d "$BASELINE_UNBRANDED" ] || [ ! -d "$BASELINE_AZURE" ]; then + echo "Fetching upstream baseline..." + + # Clone with sparse checkout to temp location + git clone --depth 1 --filter=blob:none --sparse \ + https://github.com/Azure/autorest.python.git "$TEMP_CLONE" + + cd "$TEMP_CLONE" + git sparse-checkout set \ + packages/typespec-python/test/unbranded/generated \ + packages/typespec-python/test/azure/generated + + cd ~/Desktop/github/typespec/packages/http-client-python + + # Copy to baseline locations + rm -rf "$BASELINE_UNBRANDED" "$BASELINE_AZURE" + cp -r "$TEMP_CLONE/packages/typespec-python/test/unbranded/generated" "$BASELINE_UNBRANDED" + cp -r "$TEMP_CLONE/packages/typespec-python/test/azure/generated" "$BASELINE_AZURE" + + # Cleanup temp clone + rm -rf "$TEMP_CLONE" + + echo "Baseline cached in generator/test/*/.baseline/" +else + echo "Using cached baseline (delete generator/test/*/.baseline to refresh)" +fi +``` + +### Step 3: Generate diff summary + +Create a markdown summary file with package-level comparison: + +```bash +cd ~/Desktop/github/typespec/packages/http-client-python + +LOCAL_UNBRANDED="generator/test/unbranded/generated" +LOCAL_AZURE="generator/test/azure/generated" +BASELINE_UNBRANDED="generator/test/unbranded/.baseline" +BASELINE_AZURE="generator/test/azure/.baseline" + +cat > generator/test/.diff-summary.md << EOF +# Diff Summary: Local vs Upstream (autorest.python) + +Generated: $(date) + +## Unbranded Packages + +| Metric | Count | +|--------|-------| +| Local | $(ls "$LOCAL_UNBRANDED" 2>/dev/null | wc -l | tr -d ' ') | +| Upstream | $(ls "$BASELINE_UNBRANDED" 2>/dev/null | wc -l | tr -d ' ') | + +### Missing locally (in upstream only): +$(comm -13 <(ls "$LOCAL_UNBRANDED" 2>/dev/null | sort) <(ls "$BASELINE_UNBRANDED" 2>/dev/null | sort) | sed 's/^/- /' || echo "- (none)") + +### New locally (not in upstream): +$(comm -23 <(ls "$LOCAL_UNBRANDED" 2>/dev/null | sort) <(ls "$BASELINE_UNBRANDED" 2>/dev/null | sort) | sed 's/^/- /' || echo "- (none)") + +## Azure Packages + +| Metric | Count | +|--------|-------| +| Local | $(ls "$LOCAL_AZURE" 2>/dev/null | wc -l | tr -d ' ') | +| Upstream | $(ls "$BASELINE_AZURE" 2>/dev/null | wc -l | tr -d ' ') | + +### Missing locally (in upstream only): +$(comm -13 <(ls "$LOCAL_AZURE" 2>/dev/null | sort) <(ls "$BASELINE_AZURE" 2>/dev/null | sort) | sed 's/^/- /' || echo "- (none)") + +### New locally (not in upstream): +$(comm -23 <(ls "$LOCAL_AZURE" 2>/dev/null | sort) <(ls "$BASELINE_AZURE" 2>/dev/null | sort) | sed 's/^/- /' || echo "- (none)") + +--- + +## Viewing Detailed Diffs + +### List changed files in a package: +\`\`\`bash +diff -rq generator/test/unbranded/generated/PACKAGE generator/test/unbranded/.baseline/PACKAGE +\`\`\` + +### View full diff for a package: +\`\`\`bash +diff -r generator/test/unbranded/generated/PACKAGE generator/test/unbranded/.baseline/PACKAGE +\`\`\` + +### Open side-by-side diff in VS Code: +\`\`\`bash +code --diff generator/test/unbranded/.baseline/PACKAGE/path/to/file.py generator/test/unbranded/generated/PACKAGE/path/to/file.py +\`\`\` + +EOF +``` + +### Step 4: Open in editor + +Open the summary file in the user's preferred editor: + +```bash +cd ~/Desktop/github/typespec/packages/http-client-python + +# Try common editors in order of preference +if command -v code &> /dev/null; then + code generator/test/.diff-summary.md +elif [ -n "$VISUAL" ]; then + $VISUAL generator/test/.diff-summary.md +elif [ -n "$EDITOR" ]; then + $EDITOR generator/test/.diff-summary.md +elif command -v vim &> /dev/null; then + vim generator/test/.diff-summary.md +else + cat generator/test/.diff-summary.md +fi +``` + +### Step 5: Report to user + +Tell the user: +- Summary file is open at `generator/test/.diff-summary.md` +- Baseline is cached alongside generated code in `generator/test/*/.baseline/` +- How to view detailed diffs (commands are in the summary file) +- How to refresh baseline: `rm -rf generator/test/*/.baseline` + +## Viewing Detailed File Diffs + +After showing the summary, if the user wants to see specific file changes: + +### Find changed files in a package: +```bash +cd ~/Desktop/github/typespec/packages/http-client-python +diff -rq \ + generator/test/unbranded/generated/typetest-array \ + generator/test/unbranded/.baseline/typetest-array +``` + +### Show inline diff for a file: +```bash +diff -u \ + generator/test/unbranded/.baseline/typetest-array/typetest/array/_client.py \ + generator/test/unbranded/generated/typetest-array/typetest/array/_client.py +``` + +### VS Code side-by-side (if user has VS Code): +```bash +code --diff \ + generator/test/unbranded/.baseline/typetest-array/typetest/array/_client.py \ + generator/test/unbranded/generated/typetest-array/typetest/array/_client.py +``` + +## Integration with test-unbranded-emitter + +After running `diff-upstream`, you can immediately run tests: +- The `.last-regenerate` marker is updated +- `test-unbranded-emitter` will skip regeneration since it's fresh + +## Cleanup + +The baseline is cached for fast subsequent runs. To refresh: + +```bash +# Remove baseline (will re-fetch on next run) +rm -rf generator/test/unbranded/.baseline generator/test/azure/.baseline + +# Remove summary file +rm generator/test/.diff-summary.md +``` + +## Gitignore + +These patterns should be in `.gitignore`: +``` +generator/test/unbranded/.baseline/ +generator/test/azure/.baseline/ +generator/test/.diff-summary.md +.last-regenerate +``` + +## Interpreting Diffs + +After generating the diff, analyze what changed and report to the user whether the diffs are expected. + +### Step 1: Check what code changes were made on the branch + +First, understand what emitter changes were made: + +```bash +# Show commits on this branch +git log main..HEAD --oneline -- "emitter/src/*.ts" + +# Show the actual changes +git diff main..HEAD -- emitter/src/ +``` + +### Step 2: Correlate code changes with expected generated output changes + +Based on the emitter changes, determine what generated code SHOULD change: + +| Emitter change type | Expected generated code impact | +|---------------------|-------------------------------| +| Namespace logic refactor (behavior-preserving) | No generated code changes | +| New decorator support | Changes in files using that decorator | +| Serialization changes | Changes in `_serialization.py`, model files | +| Client initialization changes | Changes in `_client.py`, `_configuration.py` | +| Operation changes | Changes in `_operations/*.py` | + +### Step 3: Compare expected vs actual diffs + +- **If actual diffs match expected:** Tell user "The diffs are expected based on your emitter changes to [describe changes]. The generated code correctly reflects your modifications." + +- **If no diffs but expected some:** Tell user "No generated code changes detected. If you expected changes, verify your emitter modifications are being triggered by the test specs, or there may be more development needed. Should I investigate further?" + +- **If unexpected diffs appear:** Tell user "Found unexpected changes in [files]. These don't appear to match your emitter modifications. There may be unintended side effects - would you like me to investigate?" + +### Pre-existing repo differences (always present, not caused by your changes) + +These diffs exist between typespec and autorest.python repos regardless of emitter changes. They should be ignored when evaluating your changes: + +| File type | Typical diff | Cause | +|-----------|--------------|-------| +| `_patch.py` | `List[str]` → `list[str]` | Type hint modernization (Python 3.9+ style) | +| `CHANGELOG.md` | Formatting differences | Template differences | +| `README.md` | Service display names | Template/config differences | +| `pyproject.toml` / `setup.py` | Different packaging | Tooling preferences | + +**If only these files differ:** "The only diffs are pre-existing repo differences (type hints, config files), not caused by your emitter changes. Your changes are behavior-preserving." + +### Unexpected diffs (actual code changes) + +If you see differences in these files, the emitter changes ARE affecting generated code: + +- `_client.py` - Client class definitions +- `_operations/*.py` - Operation implementations +- `models/*.py` - Model definitions (excluding `_patch.py`) +- `_serialization.py` - Serialization logic +- `aio/*.py` - Async client code + +**If these files differ:** First check if the changes match what the emitter modifications should produce. If they match, say "The generated code changes are expected based on your emitter changes." If they don't match or are unexpected, say "Found changes that don't appear to match your emitter modifications. There may be more development needed - would you like me to investigate?" + +### Quick diff analysis command + +To check if there are actual code changes (excluding expected differences): + +```bash +diff -rq generator/test/unbranded/generated generator/test/unbranded/.baseline 2>/dev/null | grep "\.py differ" | grep -v "_patch.py" +``` + +If this returns nothing, only expected diffs exist. + +## Notes + +### Diff direction + +The diff shows changes from upstream (baseline) to local (your changes): +- `-` lines: removed from upstream (or changed) +- `+` lines: added in your local version + +### Why packages might be missing locally + +Some upstream packages may not generate locally due to: +- TypeSpec version incompatibilities in the monorepo +- Specs that require specific dependencies not installed +- Test configurations that differ between repos + +This is expected for ARM/azure-resource-manager specs if there are version conflicts. + +### Performance + +- First run: ~30-60s (fetch baseline) +- Subsequent runs: instant (uses cached baseline) +- Regenerate: ~2-3 minutes (always runs for diff) diff --git a/packages/http-client-python/.github/skills/test-unbranded-emitter/SKILL.md b/packages/http-client-python/.github/skills/test-unbranded-emitter/SKILL.md new file mode 100644 index 00000000000..0cdbd23f005 --- /dev/null +++ b/packages/http-client-python/.github/skills/test-unbranded-emitter/SKILL.md @@ -0,0 +1,175 @@ +--- +name: test-unbranded-emitter +description: > + Build and test the @typespec/http-client-python emitter. Use this skill whenever + the user wants to test their local changes to the emitter, run the generator + test suite, check if their http-client-python changes are passing, or validate + a fix. Triggers on phrases like "test the emitter", "run tests", "check if my + changes pass", "run test:generator", "run CI", or any mention of testing/validating + changes in the emitter package. +--- + +# Test Emitter Skill + +Builds and tests `@typespec/http-client-python` to validate local changes. +Automatically regenerates if the generated code is stale. + +## Paths + +All paths are relative to the http-client-python package root: +`~/Desktop/github/typespec/packages/http-client-python` + +| Item | Path | +|------|------| +| Package root | `~/Desktop/github/typespec/packages/http-client-python` | +| Emitter source | `emitter/src` | +| Generated (unbranded) | `generator/test/unbranded/generated` | +| Generated (azure) | `generator/test/azure/generated` | +| Regenerate marker | `.last-regenerate` | + +## Workflow + +### Step 1: Build the emitter + +```bash +cd ~/Desktop/github/typespec/packages/http-client-python +npm run build +``` + +Check for TypeScript compilation errors. If the build fails, report the errors +to the user and stop — do not proceed to testing. + +### Step 2: Check if regeneration is needed + +Regeneration is needed if ANY of these conditions are true: + +1. **Generated folder doesn't exist or is empty**: + ```bash + cd ~/Desktop/github/typespec/packages/http-client-python + + if [ ! -d "generator/test/unbranded/generated" ] || \ + [ -z "$(ls -A generator/test/unbranded/generated 2>/dev/null)" ]; then + echo "Unbranded generated folder is empty - regeneration needed" + fi + + if [ ! -d "generator/test/azure/generated" ] || \ + [ -z "$(ls -A generator/test/azure/generated 2>/dev/null)" ]; then + echo "Azure generated folder is empty - regeneration needed" + fi + ``` + +2. **No regenerate marker file exists**: + ```bash + if [ ! -f ".last-regenerate" ]; then + echo "No regenerate marker - regeneration needed" + fi + ``` + +3. **Emitter source files are newer than the marker**: + ```bash + if [ -n "$(find emitter/src -newer .last-regenerate -type f 2>/dev/null | head -1)" ]; then + echo "Emitter source changed since last regenerate - regeneration needed" + fi + ``` + +### Step 3: Regenerate if needed + +If any condition from Step 2 is true: + +```bash +cd ~/Desktop/github/typespec/packages/http-client-python +echo "Regenerating test clients..." +npm run regenerate +``` + +After successful regeneration, update the marker: + +```bash +touch .last-regenerate +``` + +If regeneration fails, report the error and stop. + +If regeneration is NOT needed, inform the user: +> "Generated code is up to date (source unchanged since last regeneration). Skipping regenerate." + +### Step 4: Run the generator test suite + +```bash +cd ~/Desktop/github/typespec/packages/http-client-python +npm run ci +``` + +This runs: +1. `test:emitter` - vitest unit tests for the emitter +2. `ci:generator` - pytest tests against generated code + +The full CI can take several minutes. Keep the user informed that it's running. + +### Step 5: Report results + +- **If tests pass**: Confirm success and summarize any notable output (warnings, skipped tests, etc.) +- **If tests fail**: Show the failing test names and error messages clearly. + Offer to help debug or investigate specific failures. + +## Integration with diff-upstream + +Both skills share the regeneration marker system: + +- `diff-upstream` always regenerates and updates `.last-regenerate` +- `test-unbranded-emitter` checks this marker and skips regeneration if fresh + +**Efficient workflow**: +``` +diff-upstream → test-unbranded-emitter +``` +The second command will skip regeneration since `diff-upstream` just did it. + +**Standalone workflow**: +``` +test-unbranded-emitter +``` +Will regenerate only if source changed since last run. + +## Notes + +### Regeneration takes time + +`npm run regenerate` can take 2-3 minutes. The staleness check avoids this delay +when the user is iterating on test fixes (not emitter changes). + +### Test types + +| Command | What it runs | Duration | +|---------|--------------|----------| +| `npm run test:emitter` | vitest unit tests only | ~5 seconds | +| `npm run test:generator` | pytest only (no vitest) | ~2 minutes | +| `npm run ci` | vitest + pytest (full CI) | ~2-3 minutes | + +This skill runs `npm run ci` (full CI). If the user wants just unit tests or +just pytest, they can ask specifically. + +### Known flaky test + +The test server teardown sometimes fails with `ProcessLookupError` - this is a +pre-existing infrastructure issue, not a test failure. All actual tests may pass +even if you see this error at the end. + +### Force regeneration + +If the user suspects stale generated code despite the marker, they can force +regeneration: + +```bash +rm ~/Desktop/github/typespec/packages/http-client-python/.last-regenerate +``` + +Then run the skill again. + +## Gitignore + +Ensure this entry is in the package's `.gitignore`: + +``` +.last-regenerate +``` diff --git a/packages/http-client-python/.gitignore b/packages/http-client-python/.gitignore new file mode 100644 index 00000000000..5cdaba90a6c --- /dev/null +++ b/packages/http-client-python/.gitignore @@ -0,0 +1,9 @@ +# Diff upstream baseline cache (fetched from autorest.python for comparison) +generator/test/unbranded/.baseline/ +generator/test/azure/.baseline/ + +# Diff summary output file +generator/test/.diff-summary.md + +# Regeneration marker (tracks when generated code was last regenerated) +.last-regenerate From 4b4e0d95461e158f6c7a97cba287be7282fb3149 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Mon, 2 Mar 2026 17:15:55 -0500 Subject: [PATCH 3/3] add changeset --- .../python-cleanUpNamespaceLogic-2026-2-2-17-15-49.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/python-cleanUpNamespaceLogic-2026-2-2-17-15-49.md diff --git a/.chronus/changes/python-cleanUpNamespaceLogic-2026-2-2-17-15-49.md b/.chronus/changes/python-cleanUpNamespaceLogic-2026-2-2-17-15-49.md new file mode 100644 index 00000000000..d8dca839ac7 --- /dev/null +++ b/.chronus/changes/python-cleanUpNamespaceLogic-2026-2-2-17-15-49.md @@ -0,0 +1,7 @@ +--- +changeKind: internal +packages: + - "@typespec/http-client-python" +--- + +clean up namespace logic in python \ No newline at end of file