Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@typespec/http-client-python"
---

Fix transient mypy CI failures on Windows with retry logic and update CONTRIBUTING docs to reflect the new end-to-end release workflow
65 changes: 43 additions & 22 deletions packages/http-client-python/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,37 +105,58 @@ Before creating a pull request:

## Downstream Testing

Due to the integration with `@azure-tools/typespec-python`, we require downstream testing to ensure compatibility.
This package (`@typespec/http-client-python`) is the **unbranded emitter**. It is wrapped by the **branded emitter** (`@azure-tools/typespec-python`), which lives in [Azure/typespec-azure](https://github.com/Azure/typespec-azure/tree/main/packages/typespec-python).

### Automatic Downstream PR Creation
### How CI Works

After your PR is created and CI passes:
When you open a PR against this package:

1. **Get the build artifact URL**:
- In your PR's CI results, click on "5 published; 1 consumed" (or similar)
- Navigate to: `Published artifacts` → `build_artifacts_python` → `packages` → `typespec-http-client-python-x.x.x.tgz`
- Click the three dots and select "Copy download url"
1. **Unbranded emitter CI** runs automatically (build, lint, test, regenerate).
2. **Branded emitter CI** also runs automatically — it builds `@azure-tools/typespec-python` from [`Azure/typespec-azure`](https://github.com/Azure/typespec-azure/tree/main/packages/typespec-python) against your PR's version of `@typespec/http-client-python` to verify compatibility.

2. **Trigger downstream testing**:
- Run [this pipeline](https://dev.azure.com/azure-sdk/internal/_build?definitionId=7257) with:
- `PULL-REQUEST-URL`: Your PR URL from step 1
- `ARTIFACTS_URL`: The artifact URL from step 1
Both must pass before your PR can be merged.

3. **Review downstream changes**:
- The pipeline will create a PR in [autorest.python](https://github.com/Azure/autorest.python)
- Follow the [autorest.python CONTRIBUTING.md](https://github.com/Azure/autorest.python/blob/main/CONTRIBUTING.md) for any additional changes needed
### Manual Regeneration Testing

4. **Merge process**:
- Ensure the downstream PR passes all tests
- Merge your original TypeSpec PR once downstream testing is complete
You can manually trigger the [TypeSpec Python Regenerate Tests](https://github.com/Azure/azure-sdk-for-python/actions/workflows/typespec-python-regenerate.yml) workflow in `azure-sdk-for-python` to regenerate tests with either emitter:

### Post-Release Updates
- **Branded** (`@azure-tools/typespec-python`): Select "branded" and optionally specify a version. If no version is given, it uses the version from [`eng/emitter-package.json`](https://github.com/Azure/azure-sdk-for-python/blob/main/eng/emitter-package.json).
- **Unbranded** (`@typespec/http-client-python`): Select "unbranded" and optionally specify a version. If no version is given, it uses the latest published version on npm.

After your changes are released:
The workflow checks out `microsoft/typespec` (at the ref you specify, defaulting to `main`), builds the regeneration infrastructure, installs the target emitter from npm, and runs the full regeneration.

1. Update the [autorest.python](https://github.com/Azure/autorest.python) repository to use the released version
2. Run `pnpm install` to update dependency mappings
3. Release the autorest emitters with your changes
### Post-Release: Updating azure-sdk-for-python

Once a new version of the branded emitter (`@azure-tools/typespec-python`) is released, follow these steps to update `azure-sdk-for-python`:

1. **Update `eng/emitter-package.json`** in [Azure/azure-sdk-for-python](https://github.com/Azure/azure-sdk-for-python):

Update the `@azure-tools/typespec-python` version to the newly released version:

```json
{
"dependencies": {
"@azure-tools/typespec-python": "<new-version>"
}
}
```

2. **Regenerate config files** using `tsp-client`:

```bash
tsp-client generate-config-files \
--package-json= < path-to-local-typespec-azure > /packages/typespec-python/package.json
```

This updates the `devDependencies` in `eng/emitter-package.json` to match the branded emitter's peer dependencies.

3. **Create a PR** with the updated `eng/emitter-package.json` and submit it to `azure-sdk-for-python`.

4. **Automatic regeneration**: Once the PR merges to `main`, the [TypeSpec Python Regenerate Tests](https://github.com/Azure/azure-sdk-for-python/actions/workflows/typespec-python-regenerate.yml) workflow triggers automatically (it watches for changes to `eng/emitter-package.json`). It regenerates all test code and creates a follow-up PR with the updated generated files.

5. **Generated code location**: The regenerated tests are checked in at [`eng/tools/azure-sdk-tools/emitter/generated/`](https://github.com/Azure/azure-sdk-for-python/tree/main/eng/tools/azure-sdk-tools/emitter/generated) in `azure-sdk-for-python`, split into:
- `azure/` — Tests generated with the branded emitter (Azure SDK specs)
- `unbranded/` — Tests generated with the unbranded emitter (TypeSpec HTTP specs)

## Getting Help

Expand Down
41 changes: 24 additions & 17 deletions packages/http-client-python/eng/scripts/ci/run_mypy.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,34 @@ def get_config_file_location():
return os.path.join(os.path.dirname(__file__), "config/mypy.ini")


def _single_dir_mypy(mod):
def _single_dir_mypy(mod, retries=2):
inner_class = get_package_namespace_dir(mod)
if not inner_class:
logging.info(f"No package directory found in {mod}, skipping")
return True
try:
check_call(
[
sys.executable,
"-m",
"mypy",
"--config-file",
get_config_file_location(),
"--ignore-missing",
str(inner_class.absolute()),
]
)
return True
except CalledProcessError as e:
logging.error("{} exited with mypy error {}".format(inner_class.stem, e.returncode))
return False
for attempt in range(1, retries + 2):
try:
check_call(
[
sys.executable,
"-m",
"mypy",
"--config-file",
get_config_file_location(),
"--ignore-missing",
str(inner_class.absolute()),
]
)
return True
except CalledProcessError as e:
if attempt <= retries:
logging.warning(
"{} mypy attempt {} failed (exit {}), retrying...".format(inner_class.stem, attempt, e.returncode)
)
else:
logging.error("{} exited with mypy error {}".format(inner_class.stem, e.returncode))
return False
return False


if __name__ == "__main__":
Expand Down
Loading