From d517979c8f3fd352afb7747ca07363737206272b Mon Sep 17 00:00:00 2001 From: Zixin Yao Date: Mon, 27 Apr 2026 18:53:34 -0700 Subject: [PATCH] Expand azure-search-documents skill with full release workflow The existing skill on main covered only the post-regeneration _patch.py audit (SKILL.md + customizations.md). This commit expands it into the full release workflow (Phase 0-7: scope, generate, sync, test, lint, changelog, samples, version) and reorganizes the customization material so each file has a single concern. - SKILL.md: rewritten as the workflow backbone; _patch.py reconciliation is now Phase 2 of a larger pipeline. - references/architecture.md (new): source layout, branching strategy, CHANGELOG conventions. - references/testing.md (new): unit and live test workflow. - references/customizations.md: Generated-vs-Handwritten rules, _patch.py map, Patterns Reference, per-file inventory (lifted out of SKILL.md tail and architecture material). - scripts/run-in-venv.ps1 (new): portable per-call wrapper that resolves the package .venv from any CWD. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/azure-search-documents/SKILL.md | 263 +++++------- .../references/architecture.md | 52 +++ .../references/customizations.md | 397 +++++++++++++----- .../references/testing.md | 53 +++ .../scripts/run-in-venv.ps1 | 45 ++ 5 files changed, 557 insertions(+), 253 deletions(-) create mode 100644 sdk/search/azure-search-documents/.github/skills/azure-search-documents/references/architecture.md create mode 100644 sdk/search/azure-search-documents/.github/skills/azure-search-documents/references/testing.md create mode 100644 sdk/search/azure-search-documents/.github/skills/azure-search-documents/scripts/run-in-venv.ps1 diff --git a/sdk/search/azure-search-documents/.github/skills/azure-search-documents/SKILL.md b/sdk/search/azure-search-documents/.github/skills/azure-search-documents/SKILL.md index 475fff5bbf73..a291dc4ed30d 100644 --- a/sdk/search/azure-search-documents/.github/skills/azure-search-documents/SKILL.md +++ b/sdk/search/azure-search-documents/.github/skills/azure-search-documents/SKILL.md @@ -1,228 +1,175 @@ --- name: azure-search-documents -description: 'Post-regeneration customization guide for azure-search-documents. Verifies _patch.py imports, ApiVersion, enum aliases, mypy/pylint, and changelog after codegen. USE WHEN: running tsp-client update, regenerating from TypeSpec, fixing _patch.py breaks, bumping ApiVersion, adding operation wrappers, or updating search SDK documentation.' +description: "**WORKFLOW SKILL** — Orchestrate the full release cycle for azure-search-documents SDK including TypeSpec generation, _patch.py customization audit, testing, linting, changelog, and versioning. WHEN: \"search SDK release\", \"regenerate search SDK\", \"update search API version\", \"search pre-release validation\", \"fix search test failures\", \"search changelog\", \"fix _patch.py breaks\", \"bump ApiVersion\", \"add operation wrappers\". REQUIRES: azure-sdk-mcp tools. FOR SINGLE OPERATIONS: Use azsdk MCP tools directly." --- -# azure-search-documents — Post-Regeneration Customization Guide +# azure-search-documents — Package Skill -After running `tsp-client update`, the generated code is a raw skeleton — not a shippable SDK. This skill tells you exactly what customizations exist in `_patch.py` files and how to verify they still work after regeneration. +## When to Use This Skill -The generator never touches `_patch.py` files, so your customizations survive regeneration. But they import from generated modules that **do** change. A renamed model, a new parameter, or a removed enum value will silently break a `_patch.py` file. The verification steps below catch these breaks. +Activate when user wants to: +- Prepare a new GA or preview release +- Regenerate SDK from TypeSpec (new API version or current spec) +- Run pre-release validation (tests, linting, checks) +- Fix `_patch.py` files broken by a regeneration +- Add convenience wrappers for newly-generated operations -## Step 1: Run Regeneration +## Prerequisites -```bash -cd sdk/search/azure-search-documents - -# Update tsp-location.yaml with the new spec commit SHA, then: -tsp-client update - -# If API version changed, update _metadata.json too -``` - -## Step 2: Verify Imports in All `_patch.py` Files - -Every `_patch.py` with customizations imports from generated modules. After regeneration, check that these imports still resolve. The fastest way: - -```bash -python -c "from azure.search.documents import SearchClient" -python -c "from azure.search.documents.aio import SearchClient" -python -c "from azure.search.documents.indexes.models import SearchField, SearchFieldDataType" -python -c "from azure.search.documents.models import IndexDocumentsBatch" -``` - -If any fail, a generated class/enum was renamed or removed. Read `references/customizations.md` for the exact import each `_patch.py` depends on. - -The `_patch.py` files that have customizations (all others are empty boilerplate): - -``` -azure/search/documents/ -├── _patch.py # SearchClient, SearchIndexingBufferedSender, ApiVersion -├── _operations/_patch.py # SearchItemPaged, search(), document CRUD -├── models/_patch.py # IndexDocumentsBatch, RequestEntityTooLargeError -├── aio/_patch.py # Async SearchClient, async SearchIndexingBufferedSender -├── aio/_operations/_patch.py # AsyncSearchItemPaged, async search()/CRUD -├── indexes/_operations/_patch.py # Polymorphic delete/update, list helpers -├── indexes/models/_patch.py # SearchField, field builders, enum aliases -└── indexes/aio/_operations/_patch.py # Async index/indexer operations -``` +- Read [references/architecture.md](references/architecture.md) for source layout, branching, and CHANGELOG conventions +- Read [references/customizations.md](references/customizations.md) for the `_patch.py` map, patterns reference, and per-file inventory — this is the catalog you verify against in Phase 2 +- Read [references/testing.md](references/testing.md) for running tests and writing new tests +- Azure SDK MCP server must be running (provides `azsdk_*` tools) -## Step 3: Check ApiVersion +## Environment Setup (MANDATORY for venv tools) -After regeneration, the generated code targets the API version in `_metadata.json`. The hand-maintained `ApiVersion` enum in `_patch.py` must include this version. +Run any command that needs the venv (`python`, `pip`, `azpysdk`, `pytest`) via the `run-in-venv.ps1` wrapper. It activates the package `.venv` for the duration of one call. Plain `git` / `grep` / file commands don't need wrapping. -```bash -# 1. See what API version the generator used -python -c "import json; print(json.load(open('_metadata.json'))['apiVersion'])" +Set a session alias once, then use the short form: -# 2. See what versions the SDK currently advertises -grep -A 20 'class ApiVersion' azure/search/documents/_patch.py +```powershell +Set-Alias venv .\.github\skills\azure-search-documents\scripts\run-in-venv.ps1 -# 3. Check the current default -grep 'DEFAULT_VERSION' azure/search/documents/_patch.py +venv azpysdk pylint . +venv python -m pytest tests\ +venv pip list ``` -If the generated API version is **not** in the `ApiVersion` enum: -1. Add the new member to `ApiVersion` in `azure/search/documents/_patch.py` (e.g., `V2026_05_01_PREVIEW = "2026-05-01-preview"`) -2. Update `DEFAULT_VERSION` to point to the new member -3. Update the `ApiVersion` docstring in the class if one exists +Use `python -m pytest` rather than bare `pytest` to avoid stale-shebang issues with relocated venvs. `python -c "..."` lines below must be wrapped the same way. -Verify the default round-trips correctly: +## Steps -```bash -python -c "from azure.search.documents._patch import ApiVersion, DEFAULT_VERSION; print(DEFAULT_VERSION.value)" -``` +### Phase 0 — Determine Scope -## Step 4: Check for New Operations That Need Wrappers +Ask the user: +1. New API version or regeneration of current spec? +2. GA release or beta/preview release? +3. Target version number and release date? -Look at what changed in the generated operations files: +If new API version: get the spec **commit SHA**, **API version string** (e.g., `2026-04-01`), and the **spec PR link** (e.g., a PR in `Azure/azure-rest-api-specs`). -```bash -git diff --name-only | grep "_operations\.py" | grep -v _patch -``` +- **commit SHA** → goes into `tsp-location.yaml` for Phase 1. +- **API version string** → drives the `ApiVersion` enum and `DEFAULT_VERSION` update in Phase 2 step 3. +- **spec PR link** → keep on hand for the release PR description and CHANGELOG cross-reference; useful for reviewers tracing the SDK change back to its TypeSpec source. -If a new operation was added to the generated `_operations.py`, decide whether it needs a convenience wrapper in `_patch.py`. Operations that need wrappers: -- **Delete operations** — need the polymorphic str-or-model pattern (see "Polymorphic Delete Pattern" below) -- **Create-or-update operations** — need `prefer="return=representation"`, `match_condition`, and `etag` forwarding -- **List operations** — may need a `select` parameter or name-only projection +> [!CAUTION] +> **STOP** if the user cannot provide the commit SHA. Do not guess or use HEAD. -Operations that pass through without a wrapper (if the generated signature is already user-friendly) can be left alone — they're inherited from the generated mixin. +### Phase 1 — Generate SDK -Remember: every sync wrapper in `_operations/_patch.py` needs a matching async wrapper in `aio/_operations/_patch.py`. +1. If commit SHA is changing, update `commit` in `tsp-location.yaml`. +2. Use `azsdk_package_generate_code` to generate SDK from TypeSpec. -## Step 5: Check for Model/Enum Changes That Affect Customizations +### Phase 2 — Sync Handwritten Code with Generated Code -```bash -git diff models/_enums.py models/_models.py indexes/models/_enums.py indexes/models/_models.py -``` +After regeneration, handwritten code (primarily `_patch.py` files, plus `_version.py` at release time) must be reconciled with whatever changed in the generated layer. Use `git diff` to see exactly what changed, then update `_patch.py` to match. -Watch for: -- **Renamed enum values** — backward-compat aliases in `_patch.py` may reference old names -- **Changed model constructors** — `IndexDocumentsBatch`, `SearchField`, `SearchIndexerDataSourceConnection`, `KnowledgeBase` are subclassed in `_patch.py` and may break if the base constructor changes -- **New fields on `SearchResult`** — `_convert_search_result()` in `_operations/_patch.py` extracts `@search.*` metadata fields; new ones need to be added -- **Changed `SearchRequest` model** — `_build_search_request()` constructs this model directly; new parameters need to be wired through -- **Changed `SearchFieldDataType` model** — `indexes/models/_patch.py` monkey-patches `SearchFieldDataType.Collection` as a `staticmethod` and adds camelCase backward-compat aliases (e.g., `Int32` → `INT32`). If values are added, removed, or renamed in the generated enum, the aliases and `Collection` helper must be updated to match +The `_patch.py` map, customization patterns, and per-file inventory all live in [references/customizations.md](references/customizations.md). Keep it open while working through this phase. -## Step 6: Ensure mypy pass +#### 1. Capture full generated diff ```bash -cd sdk/search/azure-search-documents - -# Preferred — uses azpysdk CLI (install via: pip install -e eng/tools/azure-sdk-tools) -azpysdk mypy +git diff -- azure/search/documents/ ':!*/_patch.py' ``` -The package-level `mypy.ini` already ignores `_generated` internals — you only need to fix errors in `_patch.py` files and `samples/`. +This tells you exactly what was added, removed, or renamed. Run against the unstaged changes from the regenerator — do **not** diff against `origin/main`, which mixes in unrelated branch history. -## Step 7: Ensure pylint pass +#### 2. Smoke-test imports across all entrypoints -```bash -cd sdk/search/azure-search-documents +If a generated class/enum was renamed or removed, a `_patch.py` import will break. Run: -# Preferred -azpysdk pylint +```powershell +venv azpysdk import_all . ``` -The repo-level `pylintrc` already excludes `_generated/`, `_vendor/`, `tests/`, and `samples/` — only `_patch.py` customizations are linted. +This imports every public module under `azure.search.documents` and exits non-zero on the first broken import. For the exact import each `_patch.py` depends on, see the per-file inventory in `customizations.md`. -## Step 8: Update Documentation and Samples +#### 3. Reconcile `ApiVersion` enum and `DEFAULT_VERSION` -If the regeneration added new operations, models, or changed the API version, update the docs and samples: +The generated code targets a new API version (read from `_metadata.json`); the hand-maintained `ApiVersion` enum in `_patch.py` must include it. If missing: -### Changelog +1. Add the new member (e.g., `V2026_05_01_PREVIEW = "2026-05-01-preview"`). +2. Update `DEFAULT_VERSION`. +3. Update the `ApiVersion` docstring. +4. Apply the same change in **both sync and async** `_patch.py` files. +5. If `_metadata.json` is stale relative to `DEFAULT_VERSION`, update it manually. -Open `CHANGELOG.md` and find the topmost `## (Unreleased)` section. Add entries under the appropriate heading: +#### 4. Reconcile all `_patch.py` files against the diff -- **`### Features Added`** — new operations, models, parameters, or API version support -- **`### Breaking Changes`** — renamed/removed models, changed signatures, dropped API versions -- **`### Bugs Fixed`** — fixes to `_patch.py` logic, pagination, encoding, etc. +Loop through all 15 `_patch.py` files with the Phase 2 diff handy. For each: -Example entry: -```markdown -## 11.x.0bN (Unreleased) +- **Added** APIs → add convenience wrappers, re-exports to `__all__`, or model overrides as needed. +- **Removed** APIs → remove all references. Grep the name across ALL `_patch.py` files to catch the entire call chain (wrapper classes delegate to inner classes). +- **Renamed/changed** APIs → adopt the new name or signature everywhere. +- **Sync + async** — every sync `_patch.py` has an async mirror. Apply identical changes to both. +- **Search both** Python snake_case (`debug_info`) and JSON camelCase (`debugInfo`), case-insensitive. -### Features Added +When deciding whether a newly-generated operation needs a wrapper, the categories that typically do are: **delete** (polymorphic str-or-model), **create-or-update** (`prefer="return=representation"`, `match_condition`, `etag` forwarding), and **list** (may need a `select` or name-only projection). See "Patterns Reference" in `customizations.md` for canonical implementations. -- Added `create_or_update_knowledge_base` to `SearchIndexClient` -- Support for API version `2026-05-01-preview` +For model/enum diff hotspots (`SearchResult`, `SearchRequest`, `SearchFieldDataType`, subclassed models, renamed enum values), see the per-file Verify checklists and the "Patterns Reference" in `customizations.md` — every customization there names the generated symbols it depends on. -### Breaking Changes +`apiview-properties.json` is regenerated automatically by `azsdk_package_generate_code` (used as `--mapping-path` for `apistubgen`). No manual edits needed; just verify it changed in your diff if the public API surface did. -- Renamed `OldModel` to `NewModel` -``` +### Phase 3 — Update and Run Tests -If no `(Unreleased)` section exists, create one above the latest release with the next version from `_version.py`. +**1. Loop through all test files** with the Phase 2 generated-code diff handy. For each one: -### README +- **Added** APIs → add new tests for new features or operations. +- **Removed** APIs → remove or rewrite tests that call them. +- **Renamed/changed** APIs → update test references to use the new names or signatures. +- Check both sync and async test files. -If new client classes or major features were added, update `README.md`: +**2.** Run unit tests, then live tests. See [references/testing.md](references/testing.md) for commands and recording workflow. -- Add usage examples for new client classes or operations -- Update the "Key concepts" section if new resource types were introduced -- Update the listed API version in the "Getting started" section if it changed +**Gate:** All unit and live tests pass. -# Customization Patterns Reference +### Phase 4 — Build, Lint, and Checks -Each pattern below describes a customization that exists in the codebase today. After regeneration, verify each one still works. Read `references/customizations.md` for the exhaustive file-by-file inventory. +Use MCP tools to validate: -## SearchClient Constructor Reordering +1. `azsdk_package_build_code` — build and detect compilation errors. +2. `azsdk_package_run_check` with `Linting` — run pylint and mypy. Fix any errors in `_patch.py` files. The package's `mypy.ini` and the repo's `pylintrc` already exclude `_generated/`, `_vendor/`, `tests/`, and `samples/`, so only `_patch.py` customizations are checked. +3. `azsdk_package_run_check` with `Changelog`, `Cspell`, `Snippets` — run remaining checks. -`SearchClient` in `_patch.py` swaps parameter order to `(endpoint, index_name, credential)`. The generated base uses `(endpoint, credential, index_name)`. If regeneration changes the base constructor signature, update the subclass. +> For the one case where editing generated code is permitted (inline `# pylint: disable=` for lint issues), see [`customizations.md` → Editing rules](references/customizations.md#editing-rules). -## Custom Search Pagination +**Gate:** Build, lint, and all checks pass. -`search()` does **not** use standard Azure SDK paging. It uses `SearchItemPaged` / `AsyncSearchItemPaged` with a custom `SearchPageIterator` because: -- Search paginates via POST with `nextPageParameters` body, not GET with `nextLink` -- First-page metadata (facets, count, answers) must be available before iteration -- Results are converted to dicts with `@search.*` keys +### Phase 5 — Update Changelog -The continuation token is Base64-encoded JSON: `{"apiVersion": "...", "nextLink": "...", "nextPageParameters": {...}}` +Follow the CHANGELOG conventions in [references/architecture.md](references/architecture.md). Use `azsdk_package_update_changelog_content` to draft entries, then review and adjust. -Shared helpers (`_build_search_request`, `_convert_search_result`, `_pack_continuation_token`, `_unpack_continuation_token`) live in sync `_operations/_patch.py` and are imported by async. Don't duplicate them. +If no `## (Unreleased)` section exists in `CHANGELOG.md`, create one above the latest release. Use the next version from `_version.py`. -## Pipe-Delimited Semantic Encoding +**Source of truth:** the auto-generated code is authoritative — if something exists in generated code, treat it as present. Fall back to the TypeSpec config in the spec PR only when the generated code is ambiguous. -`_build_search_request()` encodes semantic parameters into pipe-delimited wire format: -``` -query_answer="extractive", count=3 → "extractive|count-3" -query_caption="extractive", highlight=True → "extractive|highlight-true" -``` -New semantic parameters should follow this pattern. +#### 2-way verification (actual code ↔ our CHANGELOG) -## SearchIndexingBufferedSender +After drafting the CHANGELOG, do a systematic cross-check: -Entirely hand-authored in `_patch.py` (sync) and `aio/_patch.py` (async). Not generated. Wraps `SearchClient` with auto-flush timer, 413 recursive batch splitting, retry per document key (409/422/503), and key field auto-detection. The async version supports both sync and async callbacks via `asyncio.iscoroutinefunction()`. +1. **Code → our CHANGELOG**: For every item in actual generated code that changed, verify it's reflected in our CHANGELOG. Use the Phase 2 `azpysdk import_all .` (catches removed/renamed symbols at import time), plus targeted `python -c "from … import X; print(X)"` checks for individual symbols. +2. **Our CHANGELOG → code**: For every item in our CHANGELOG, verify it matches actual code (e.g., removed properties are truly absent, new models actually exist). -## Field Builders +#### Sorting -`SimpleField()`, `SearchableField()`, `ComplexField()` in `indexes/models/_patch.py`. `SimpleField` explicitly sets `searchable=False`. `SearchableField` auto-sets type to `String`/`Collection(String)`. +All lists within each CHANGELOG section should be sorted alphabetically by fully qualified name. -`SearchField` subclass adds `hidden` property (inverse of `retrievable`). +### Phase 6 — Update Samples and README -`SearchFieldDataType.Collection` is a `staticmethod` monkey-patched onto the generated enum. +1. **Samples**: Update existing samples and add new ones for any new features or changed APIs. +2. **README**: + - Add usage examples for new client classes or operations. + - Update the "Key concepts" section if new resource types were introduced. + - Update the listed API version in the "Getting started" section if it changed. -## Backward-Compatible Enum Aliases +### Phase 7 — Update Version and Metadata -Monkey-patched at module load in `_patch.py` files: -```python -SearchFieldDataType.Int32 = SearchFieldDataType.INT32 # camelCase → UPPER -``` -After regeneration, verify the right-hand-side names still exist in the generated enums. If a new enum collides with a Python keyword, add an alias. - -## Polymorphic Delete Pattern - -All delete/update operations in `indexes/_operations/_patch.py` accept str or model object: -```python -def delete_index(self, index, *, match_condition=MatchConditions.Unconditionally, **kwargs): - try: - name = index.name # model object - return self._delete_index(name=name, etag=index.e_tag, match_condition=match_condition, **kwargs) - except AttributeError: - name = index # string - return self._delete_index(name=name, **kwargs) -``` -New resource types need this same wrapper in both sync and async. +Use `azsdk_package_update_version` and `azsdk_package_update_metadata` to bump the version and update package metadata. -## `_convert_index_response` Helper +## Reference Files -`list_indexes(select=...)` returns `SearchIndexResponse` (projection type). `_convert_index_response()` maps it to `SearchIndex`, notably `response.semantic` → `semantic_search`. Shared between sync and async via import. +| File | Contents | +|------|----------| +| [references/architecture.md](references/architecture.md) | Source layout, branching, CHANGELOG conventions | +| [references/customizations.md](references/customizations.md) | `_patch.py` map, patterns reference, per-file inventory | +| [references/testing.md](references/testing.md) | Running tests, writing new tests, test recording | diff --git a/sdk/search/azure-search-documents/.github/skills/azure-search-documents/references/architecture.md b/sdk/search/azure-search-documents/.github/skills/azure-search-documents/references/architecture.md new file mode 100644 index 000000000000..18b8419a9d28 --- /dev/null +++ b/sdk/search/azure-search-documents/.github/skills/azure-search-documents/references/architecture.md @@ -0,0 +1,52 @@ +# azure-search-documents — Architecture + +For everything related to `_patch.py` (the map, patterns, per-file inventory), see [customizations.md](customizations.md). + +## Source Layout + +Package: `sdk/search/azure-search-documents/` + +``` +tsp-location.yaml # TypeSpec spec pointer (commit SHA, directory, repo) +pyproject.toml # Package metadata, version, dependencies +CHANGELOG.md # Release notes +assets.json # Test recording tag +azure/search/documents/ # Source code (generated + handwritten — see customizations.md) +tests/ # All tests (sync, async, playback, live) +samples/ # All samples (sync, async) +``` + +## Branching Strategy + +Two parallel release tracks: + +- **Preview releases** — build from main, so the latest preview contains all features. New branch: `search/` (e.g. `search/2026-05-01-preview`). +- **GA releases** — build from the previous GA. New branch: `search/-ga` (e.g. `search/2026-05-01-ga`). + +## CHANGELOG Conventions + +Each release entry uses up to three sub-headings: + +- **`### Features Added`** — new APIs, new operations, new optional parameters, new API-version support. +- **`### Breaking Changes`** — removed/renamed types or properties, removed required parameters, removed operations. See section structure below. +- **`### Bugs Fixed`** — fixes to `_patch.py` logic (pagination, encoding, retry, etc.). + +### Preview releases + +- **Features Added**: New relative to the **previous preview**. +- **Breaking Changes**: Relative to the previous preview. Start with: + ``` + > These changes do not impact the API of stable versions such as . + > Only code written against a beta version such as may be affected. + ``` + +### GA releases + +- **Features Added**: New relative to the **previous GA** (not the latest preview). +- **Breaking Changes** has two sections: + 1. **GA-to-GA breaking changes** (before disclaimer): Changes that break compatibility with the previous GA. Listed first, no disclaimer needed. + 2. **Preview-to-GA breaking changes** (after disclaimer): Preview-only items that did not graduate to GA. Start with: + ``` + > These changes do not impact the API of stable versions such as . + > Only code written against a beta version such as may be affected. + ``` diff --git a/sdk/search/azure-search-documents/.github/skills/azure-search-documents/references/customizations.md b/sdk/search/azure-search-documents/.github/skills/azure-search-documents/references/customizations.md index 7f5d99d19f71..f608ebdde29f 100644 --- a/sdk/search/azure-search-documents/.github/skills/azure-search-documents/references/customizations.md +++ b/sdk/search/azure-search-documents/.github/skills/azure-search-documents/references/customizations.md @@ -1,52 +1,202 @@ -# Post-Regeneration Customization Checklist +# azure-search-documents — Customizations -Use this file after running `tsp-client update` to verify every customization. Each section is a `_patch.py` file with the exact classes, functions, and aliases it defines, plus what generated symbols it depends on. +This file is the catalog you verify against after every regeneration. It has four parts: + +1. **Generated vs Handwritten** — the editing rule. +2. **`_patch.py` Map** — where each customization file lives. +3. **Patterns Reference** — narrative tour of each customization pattern, why it exists, and what to watch for after regeneration. +4. **Per-File Inventory** — for each `_patch.py`: what it depends on, what it defines (named symbols only), and what to verify after regen. + +--- + +## Generated vs Handwritten + +This SDK uses **TypeSpec** code generation. All files except `_patch.py` and `_version.py` are generated and overwritten on regeneration. + +#### Editing rules + +- **Only edit `_patch.py` files.** They customize generated behavior (convenience methods, parameter normalization, response transformation, re-exports). Each sync `_patch.py` has an async mirror under `aio/`. +- **Sole exception:** inline `# pylint: disable=` comments may be added to generated files for lint issues. This is the only case where editing generated code is allowed. + +#### Docstrings on wrappers + +When a `_patch.py` method wraps a generated method, copy the generated method's docstring as your starting point. Only adjust the parts that no longer apply — e.g. a parameter you removed, a parameter you added, a return type you changed. Everything else (descriptions, types, exceptions) should stay identical to the generated docstring. + +#### Why this catalog exists + +The generator never touches `_patch.py` files, so customizations survive regeneration. But they import from generated modules that **do** change. A renamed model, a new parameter, or a removed enum value will silently break a `_patch.py` file. The patterns and per-file inventory below tell you what to verify. + +--- + +## `_patch.py` Map + +4 sub-packages, 15 files total. All must be audited after every regeneration. + +``` +documents/ # SearchClient +├── _patch.py # SearchClient, SearchIndexingBufferedSender, ApiVersion, DEFAULT_VERSION, audience handling +├── _operations/_patch.py # SearchItemPaged, search(), document CRUD, 413 splitting +├── models/_patch.py # IndexDocumentsBatch, RequestEntityTooLargeError +├── aio/_patch.py # Async SearchClient + async SearchIndexingBufferedSender, audience handling +├── aio/_operations/_patch.py # AsyncSearchItemPaged, async search()/CRUD +├── indexes/ # SearchIndexClient + SearchIndexerClient +│ ├── _patch.py # SearchIndexClient, SearchIndexerClient (audience handling) +│ ├── _operations/_patch.py # Polymorphic delete/update, list helpers, _convert_index_response +│ ├── models/_patch.py # SearchField, field builders, enum aliases, Collection staticmethod +│ ├── aio/_patch.py # Async SearchIndexClient + SearchIndexerClient (audience handling) +│ └── aio/_operations/_patch.py # Async polymorphic delete/update, async list helpers +└── knowledgebases/ # KnowledgeBaseRetrievalClient + ├── _patch.py # KnowledgeBaseRetrievalClient (audience handling) + ├── _operations/_patch.py # (empty) + ├── models/_patch.py # (empty) + ├── aio/_patch.py # Async KnowledgeBaseRetrievalClient (audience handling) + └── aio/_operations/_patch.py # (empty) +``` --- -## File: `azure/search/documents/_patch.py` +## Patterns Reference + +Each pattern below describes a customization that exists in the codebase today. After regeneration, verify each one still works. Read the per-file inventory below for the exhaustive file-by-file breakdown. + +### SearchClient Constructor Reordering + +`SearchClient` in `_patch.py` swaps parameter order to `(endpoint, index_name, credential)`. The generated base uses `(endpoint, credential, index_name)`. If regeneration changes the base constructor signature, update the subclass. + +```python +class SearchClient(_SearchClient): + def __init__( + self, endpoint: str, index_name: str, credential: Union[AzureKeyCredential, TokenCredential], **kwargs: Any + ) -> None: + audience = kwargs.pop("audience", None) + if audience: + kwargs.setdefault("credential_scopes", [audience.rstrip("/") + "/.default"]) + super().__init__(endpoint=endpoint, credential=credential, index_name=index_name, **kwargs) +``` + +### Custom Search Pagination + +`search()` does **not** use standard Azure SDK paging. It uses `SearchItemPaged` / `AsyncSearchItemPaged` with a custom `SearchPageIterator` because: +- Search paginates via POST with `nextPageParameters` body, not GET with `nextLink` +- First-page metadata (facets, count, answers) must be available before iteration +- Results are converted to dicts with `@search.*` keys -### Depends On (from generated code) +The continuation token is Base64-encoded JSON: `{"apiVersion": "...", "nextLink": "...", "nextPageParameters": {...}}` + +Shared helpers (`_build_search_request`, `_convert_search_result`, `_pack_continuation_token`, `_unpack_continuation_token`) live in sync `_operations/_patch.py` and are imported by async. Don't duplicate them. + +### Pipe-Delimited Semantic Encoding + +`_build_search_request()` encodes semantic parameters into pipe-delimited wire format: +``` +query_answer="extractive", count=3 → "extractive|count-3" +query_caption="extractive", highlight=True → "extractive|highlight-true" +``` +New semantic parameters should follow this pattern. + +### SearchIndexingBufferedSender + +Entirely hand-authored in `_patch.py` (sync) and `aio/_patch.py` (async). Not generated. Wraps `SearchClient` with auto-flush timer, 413 recursive batch splitting, retry per document key (409/422/503), and key field auto-detection. The async version supports both sync and async callbacks via `asyncio.iscoroutinefunction()`. + +### Field Builders + +`SimpleField()`, `SearchableField()`, `ComplexField()` in `indexes/models/_patch.py`. `SimpleField` explicitly sets `searchable=False`. `SearchableField` auto-sets type to `String`/`Collection(String)`. + +`SearchField` subclass adds `hidden` property (inverse of `retrievable`). + +`SearchFieldDataType.Collection` is a `staticmethod` monkey-patched onto the generated enum. + +### Backward-Compatible Enum Aliases + +Monkey-patched at module load in `_patch.py` files: +```python +SearchFieldDataType.Int32 = SearchFieldDataType.INT32 # camelCase → UPPER +``` +After regeneration, verify the right-hand-side names still exist in the generated enums. If a new enum collides with a Python keyword, add an alias. + +### Polymorphic Delete Pattern + +All delete/update operations in `indexes/_operations/_patch.py` accept str or model object: +```python +def delete_index(self, index, *, match_condition=MatchConditions.Unconditionally, **kwargs): + try: + name = index.name # model object + return self._delete_index(name=name, etag=index.e_tag, match_condition=match_condition, **kwargs) + except AttributeError: + name = index # string + return self._delete_index(name=name, **kwargs) +``` +New resource types need this same wrapper in both sync and async. + +### `_convert_index_response` Helper + +`list_indexes(select=...)` returns `SearchIndexResponse` (projection type). `_convert_index_response()` maps it to `SearchIndex`, notably `response.semantic` → `semantic_search`. Shared between sync and async via import. + +### `audience` → `credential_scopes` Translation + +All 6 client subclasses (`SearchClient`, `SearchIndexClient`, `SearchIndexerClient`, `KnowledgeBaseRetrievalClient` × sync/async) accept an `audience` kwarg and translate it to `credential_scopes` before delegating to the generated base: + +```python +audience = kwargs.pop("audience", None) +if audience: + kwargs.setdefault("credential_scopes", [audience.rstrip("/") + "/.default"]) +super().__init__(endpoint=endpoint, credential=credential, **kwargs) +``` + +This is a deliberate API-review customization — `audience` is the documented public-facing keyword. After regeneration, every new client class needs the same translation in **both sync and async**. `SearchIndexingBufferedSender` also pops `audience` (and `api_version`) from kwargs for the same reason. + +--- + +## Per-File Inventory + +Use this section after running regeneration to verify every customization. Each section is a `_patch.py` file with the exact classes, functions, and aliases it defines, plus what generated symbols it depends on. + +### File: `azure/search/documents/_patch.py` + +#### Depends On (from generated code) - `._client.SearchClient` as `_SearchClient` (base class) -### Defines +#### Defines | Symbol | Type | What It Does | |--------|------|-------------| -| `SearchClient` | class | Subclass of `_SearchClient`; reorders constructor to `(endpoint, index_name, credential)` | -| `SearchIndexingBufferedSender` | class | Hand-authored batching sender. Uses `threading.Timer` for auto-flush, recursive 413 splitting, retry per doc key (409/422/503), key field auto-detection | +| `ApiVersion` | enum | Hand-maintained list of supported API versions (e.g., `V2026_04_01`) | +| `DEFAULT_VERSION` | constant | Default API version (e.g., `ApiVersion.V2026_04_01`) | +| `SearchClient` | class | Subclass of `_SearchClient`; reorders constructor to `(endpoint, index_name, credential)`; translates `audience` kwarg → `credential_scopes` | +| `SearchIndexingBufferedSender` | class | Hand-authored batching sender. Uses `threading.Timer` for auto-flush, recursive 413 splitting, retry per doc key (409/422/503), key field auto-detection. Also pops `audience` and `api_version` from kwargs. | | `is_retryable_status_code()` | function | Returns True for 409, 422, 503 | -### After Regeneration, Verify +#### After Regeneration, Verify - [ ] `_SearchClient` base class constructor signature unchanged -- [ ] If API version changed, add new `ApiVersion` member and update default +- [ ] If API version changed, add new `ApiVersion` member and update `DEFAULT_VERSION` +- [ ] `audience` → `credential_scopes` translation still in `__init__` --- -## File: `azure/search/documents/models/_patch.py` +### File: `azure/search/documents/models/_patch.py` -### Depends On (from generated code) +#### Depends On (from generated code) - `.._generated.models.IndexDocumentsBatch` as `IndexDocumentsBatchGenerated` (base class) - `._enums.ScoringStatistics` -### Defines +#### Defines | Symbol | Type | What It Does | |--------|------|-------------| | `IndexDocumentsBatch` | class | Adds `add_upload_actions`, `add_delete_actions`, `add_merge_actions`, `add_merge_or_upload_actions`, `dequeue_actions`, `enqueue_actions`, `actions` property | | `RequestEntityTooLargeError` | class | `HttpResponseError` subclass for 413 | -### After Regeneration, Verify -- [ ] `IndexDocumentsBatchGenerated` base class still exists with compatible constructor +#### After Regeneration, Verify +- [ ] `IndexDocumentsBatchGenerated` base class still exists with a compatible constructor — if the constructor signature changed, update the subclass `__init__`. --- -## File: `azure/search/documents/_operations/_patch.py` +### File: `azure/search/documents/_operations/_patch.py` -### Depends On (from generated code) +#### Depends On (from generated code) - `..models._models.SearchRequest` (constructed directly in `_build_search_request`) - `..models._models.SearchResult` (field access in `_convert_search_result`) - `azure.core.paging.ItemPaged`, `azure.core.paging.PageIterator` -### Defines +#### Defines | Symbol | Type | What It Does | |--------|------|-------------| | `_convert_search_result(result)` | function | Extracts `@search.score`, `@search.reranker_score`, `@search.highlights`, `@search.captions`, `@search.document_debug_info`, `@search.reranker_boosted_score` | @@ -57,72 +207,139 @@ Use this file after running `tsp-client update` to verify every customization. E | `SearchItemPaged` | class | Extends `ItemPaged` with same metadata accessors | | `_SearchClientOperationsMixin` | class | Overrides: `search()`, `index_documents()` (413 splitting), `upload_documents()`, `delete_documents()`, `merge_documents()`, `merge_or_upload_documents()` | -### After Regeneration, Verify -- [ ] `SearchRequest` model fields still match what `_build_search_request` sets — new search parameters need to be wired through -- [ ] `SearchResult` model fields still match what `_convert_search_result` extracts — new `@search.*` fields need to be added -- [ ] Generated mixin methods that `_SearchClientOperationsMixin` calls (e.g., `_search_post`) still exist with same signatures +#### After Regeneration, Verify +- [ ] `SearchRequest` model fields still match what `_build_search_request` sets — for any new field on `SearchRequest`, decide whether to plumb it through `_build_search_request` (and whether it needs pipe-encoding). +- [ ] `SearchResult` model fields still match what `_convert_search_result` extracts — for any new `@search.*` key, add an extraction line. +- [ ] Generated mixin methods that `_SearchClientOperationsMixin` calls (e.g., `_search_post`, `_index_documents`) still exist with same signatures. If renamed, update the `self._xxx(...)` call sites here. --- -## File: `azure/search/documents/aio/_patch.py` +### File: `azure/search/documents/aio/_patch.py` -### Depends On (from generated code) +#### Depends On (from generated code) - `.._client.SearchClient` as async `_SearchClient` (base class) -### Defines +#### Defines | Symbol | Type | What It Does | |--------|------|-------------| -| `SearchClient` | class | Async subclass, same reorder as sync | -| `SearchIndexingBufferedSender` | class | Async version: `asyncio.Task` instead of `threading.Timer`, `iscoroutinefunction()` callback detection | +| `SearchClient` | class | Async subclass, same reorder as sync; translates `audience` kwarg → `credential_scopes` | +| `SearchIndexingBufferedSender` | class | Async version: `asyncio.Task` instead of `threading.Timer`, `iscoroutinefunction()` callback detection. Also pops `audience` and `api_version` from kwargs. | -### After Regeneration, Verify +#### After Regeneration, Verify - [ ] Same checks as sync `_patch.py` --- -## File: `azure/search/documents/aio/_operations/_patch.py` +### File: `azure/search/documents/aio/_operations/_patch.py` -### Depends On (from generated code) +#### Depends On (from generated code) - Same models as sync `_operations/_patch.py` - `azure.core.async_paging.AsyncItemPaged`, `AsyncPageIterator` -### Imports From Sync (NOT Duplicated) +#### Imports From Sync (NOT Duplicated) - `_build_search_request`, `_convert_search_result`, `_pack_continuation_token`, `_unpack_continuation_token` -### Defines +#### Defines | Symbol | Type | What It Does | |--------|------|-------------| | `AsyncSearchPageIterator` | class | Async version of `SearchPageIterator` | | `AsyncSearchItemPaged` | class | Async version of `SearchItemPaged` | | `_SearchClientOperationsMixin` | class | Async operations mixin, same methods as sync | -### After Regeneration, Verify +#### After Regeneration, Verify - [ ] Same checks as sync `_operations/_patch.py` - [ ] Imports from sync `_operations/_patch.py` still resolve --- -## File: `azure/search/documents/indexes/models/_patch.py` +### File: `azure/search/documents/indexes/_patch.py` + +#### Depends On (from generated code) +- `._client.SearchIndexClient` as `_SearchIndexClient` (base class) +- `._client.SearchIndexerClient` as `_SearchIndexerClient` (base class) + +#### Defines +| Symbol | Type | What It Does | +|--------|------|-------------| +| `SearchIndexClient` | class | Subclass; translates `audience` kwarg → `credential_scopes`, then delegates to base `__init__` | +| `SearchIndexerClient` | class | Same pattern, for indexer client | + +#### After Regeneration, Verify +- [ ] Both `_SearchIndexClient` and `_SearchIndexerClient` base constructors still accept `(endpoint, credential, **kwargs)` +- [ ] `audience` → `credential_scopes` translation still present in both `__init__`s + +--- + +### File: `azure/search/documents/indexes/aio/_patch.py` + +#### Depends On (from generated code) +- `._client.SearchIndexClient` as async `_SearchIndexClient` (base class) +- `._client.SearchIndexerClient` as async `_SearchIndexerClient` (base class) + +#### Defines +| Symbol | Type | What It Does | +|--------|------|-------------| +| `SearchIndexClient` | class | Async subclass; same `audience` → `credential_scopes` translation as sync | +| `SearchIndexerClient` | class | Async indexer client; same pattern | + +#### After Regeneration, Verify +- [ ] Same checks as sync `indexes/_patch.py` -### Depends On (from generated code) +--- + +### File: `azure/search/documents/knowledgebases/_patch.py` + +#### Depends On (from generated code) +- `._client.KnowledgeBaseRetrievalClient` as `_KnowledgeBaseRetrievalClient` (base class) + +#### Defines +| Symbol | Type | What It Does | +|--------|------|-------------| +| `KnowledgeBaseRetrievalClient` | class | Subclass; translates `audience` kwarg → `credential_scopes` | + +#### After Regeneration, Verify +- [ ] `_KnowledgeBaseRetrievalClient` base constructor still accepts `(endpoint, credential, **kwargs)` (note: `knowledge_base_name` is required and forwarded via kwargs) +- [ ] `audience` → `credential_scopes` translation still present + +--- + +### File: `azure/search/documents/knowledgebases/aio/_patch.py` + +#### Depends On (from generated code) +- `._client.KnowledgeBaseRetrievalClient` as async `_KnowledgeBaseRetrievalClient` (base class) + +#### Defines +| Symbol | Type | What It Does | +|--------|------|-------------| +| `KnowledgeBaseRetrievalClient` | class | Async subclass; same `audience` → `credential_scopes` translation as sync | + +#### After Regeneration, Verify +- [ ] Same checks as sync `knowledgebases/_patch.py` + +--- + +### File: `azure/search/documents/indexes/models/_patch.py` + +#### Depends On (from generated code) - `._models.SearchField` as `_SearchField` (base class) - `._models.SearchIndexerDataSourceConnection` as `_SearchIndexerDataSourceConnection` (base class) - `._models.KnowledgeBase` as `_KnowledgeBase` (base class) - `._enums.SearchFieldDataType`, `OcrSkillLanguage`, `SplitSkillLanguage`, `TextTranslationSkillLanguage` - Various model imports for type annotations -### Defines +#### Defines | Symbol | Type | What It Does | |--------|------|-------------| | `SearchField` | class | Adds `hidden` property (inverse of `retrievable`), constructor accepts `hidden` kwarg | -| `SearchIndexerDataSourceConnection` | class | Accepts `connection_string` str or `credentials` object | -| `KnowledgeBase` | class | Deserializes `retrieval_reasoning_effort` from dict | +| `SearchIndexerDataSourceConnection` | class | Adds `@overload`s so callers can pass either `connection_string=` *or* `credentials=` (plus a raw-mapping overload) | +| `KnowledgeBase` | class | Empty subclass placeholder (just calls `super().__init__`). Kept as an extension point for future API-review-driven customization. | | `SimpleField()` | function | Builder: sets `searchable=False` explicitly | | `SearchableField()` | function | Builder: auto-types to `String`/`Collection(String)` | | `ComplexField()` | function | Builder: sets type to `Complex`/`Collection(Complex)` | | `Collection()` | function | Wraps type in `"Collection(...)"` format | +| `_collection_helper()` | function (private) | Internal helper called by the `Collection`/`SimpleField`/`SearchableField` builders to format collection types | -### Enum Aliases +#### Enum Aliases ```python # SearchFieldDataType (old camelCase -> generated UPPER_CASE) SearchFieldDataType.String = SearchFieldDataType.STRING @@ -139,78 +356,68 @@ SearchFieldDataType.ComplexType = SearchFieldDataType.COMPLEX SearchFieldDataType.Collection = staticmethod(Collection) ``` -### After Regeneration, Verify -- [ ] All right-hand-side enum members (`STRING`, `INT32`, etc.) still exist -- [ ] `_SearchField`, `_SearchIndexerDataSourceConnection`, `_KnowledgeBase` base constructors unchanged -- [ ] No new enum values that collide with Python keywords — if so, add aliases -- [ ] `SearchField.retrievable` property still exists (used by `hidden` inversion) +#### After Regeneration, Verify +- [ ] All right-hand-side enum members (`STRING`, `INT32`, etc.) still exist — if any were renamed, update the alias RHS. +- [ ] `_SearchField`, `_SearchIndexerDataSourceConnection`, `_KnowledgeBase` base constructors unchanged (or update the subclass overloads to match). +- [ ] Any new enum value that collides with a Python keyword → add an alias. +- [ ] `SearchField.retrievable` still exists (used by the `hidden` inversion). --- -## File: `azure/search/documents/indexes/_operations/_patch.py` +### File: `azure/search/documents/indexes/_operations/_patch.py` -### Depends On (from generated code) -- Generated `_SearchIndexClientOperationsMixin` and `_SearchIndexerClientOperationsMixin` (base classes) +#### Depends On (from generated code) +- Generated `_SearchIndexClientOperationsMixin` and `_SearchIndexerClientOperationsMixin` (base classes — every wrapper below delegates to a `_xxx`-prefixed method on these) - `azure.core.match_conditions.MatchConditions` -### Defines +#### Defines | Symbol | Type | What It Does | |--------|------|-------------| -| `_convert_index_response(response)` | function | Maps `SearchIndexResponse` → `SearchIndex` (semantic → semantic_search) | - -**`_SearchIndexClientOperationsMixin`** wraps: -| Method | Customization | -|--------|--------------| -| `delete_index(index)` | Polymorphic: str or SearchIndex | -| `create_or_update_index(index)` | Adds prefer, match_condition, allow_index_downtime | -| `delete_synonym_map(synonym_map)` | Polymorphic: str or SynonymMap | -| `create_or_update_synonym_map(synonym_map)` | Adds prefer, match_condition | -| `delete_alias(alias)` | Polymorphic: str or SearchAlias | -| `create_or_update_alias(alias)` | Adds prefer, match_condition | -| `delete_knowledge_base(kb)` | Polymorphic: str or KnowledgeBase | -| `create_or_update_knowledge_base(kb)` | Adds prefer, match_condition | -| `delete_knowledge_source(ks)` | Polymorphic: str or KnowledgeSource | -| `create_or_update_knowledge_source(ks)` | Adds prefer, match_condition | -| `list_indexes(*, select)` | Uses `_convert_index_response` for projections | -| `list_index_names()` | Name-only projection via `cls` callback | -| `get_synonym_maps(*, select)` | Returns list | - -**`_SearchIndexerClientOperationsMixin`** wraps: -| Method | Customization | -|--------|--------------| -| `delete_data_source_connection(dsc)` | Polymorphic: str or object | -| `create_or_update_data_source_connection(dsc)` | Adds prefer, match_condition, skip_indexer_reset | -| `delete_indexer(indexer)` | Polymorphic: str or SearchIndexer | -| `create_or_update_indexer(indexer)` | Adds prefer, match_condition, skip/disable cache | -| `delete_skillset(skillset)` | Polymorphic: str or SearchIndexerSkillset | -| `create_or_update_skillset(skillset)` | Adds prefer, match_condition, skip/disable cache | - -### After Regeneration, Verify -- [ ] All generated `_delete_*`, `_create_or_update_*`, `_list_*` base methods still exist with compatible signatures -- [ ] `SearchIndexResponse` still has `.semantic` field (used by `_convert_index_response`) -- [ ] New resource types added to spec → add polymorphic delete/update wrappers here -- [ ] Verify prefer header value `"return=representation"` still applies - ---- - -## File: `azure/search/documents/indexes/aio/_operations/_patch.py` +| `_convert_index_response(response)` | function | Maps `SearchIndexResponse` (the projection model returned by `list_indexes(select=...)`) → `SearchIndex`. Notably remaps `response.semantic` → `semantic_search`. Shared with the async mixin via import. | +| `_SearchIndexClientOperationsMixin` | class | Override mixin for `SearchIndexClient`. See pattern categories below. | +| `_SearchIndexerClientOperationsMixin` | class | Override mixin for `SearchIndexerClient`. See pattern categories below. | + +#### Wrapper Pattern Categories + +Every method on these two mixins falls into one of the categories below. The current method roster lives in the file — `grep " def " azure/search/documents/indexes/_operations/_patch.py` to see it. Use the categories to decide what each method needs and to slot in new ones after regeneration. + +| Category | What the wrapper does | Applies to | +|---|---|---| +| **Polymorphic delete** | Accepts `str` or model object. If model, extract `.name` and pass `etag=obj.e_tag`. Always accepts `match_condition=MatchConditions.Unconditionally`. | `delete_index`, `delete_synonym_map`, `delete_alias`, `delete_knowledge_base`, `delete_knowledge_source`, `delete_data_source_connection`, `delete_indexer`, `delete_skillset` | +| **Create-or-update with prefer/match_condition** | Adds `prefer="return=representation"`, `match_condition`, and resource-specific knobs (e.g., `allow_index_downtime`, `skip_indexer_reset`, `skip_initial_run`, `disable_cache_reprocessing_change_detection`). | Every `create_or_update_*` for the resource types above | +| **List with projection** | Accepts `select=...`, returns the full model; some apply a `cls` callback (e.g., `_convert_index_response`) to map the projection model back to the canonical model. | `list_indexes`, `get_synonym_maps`, `get_skillsets`, `get_indexers`, `get_data_source_connections` | +| **Name-only list helper** | Calls the underlying list with a `cls` callback that projects to `[item.name, ...]`. | `list_index_names`, `get_synonym_map_names`, `list_alias_names`, `get_indexer_names`, `get_data_source_connection_names`, `get_skillset_names` | +| **Resource helper** | One-off convenience that doesn't fit the patterns above. | `analyze_text`, `get_index_statistics`, `get_search_client` (instantiates a `SearchClient` from the index client's endpoint + credential) | + +Polymorphic-delete reference implementation (verified against `delete_index`): +```python +def delete_index(self, index, *, match_condition=MatchConditions.Unconditionally, **kwargs): + try: + name = index.name + return self._delete_index(name=name, etag=index.e_tag, match_condition=match_condition, **kwargs) + except AttributeError: + return self._delete_index(name=index, **kwargs) +``` + +#### After Regeneration, Verify +- [ ] Diff the generated `_SearchIndexClientOperationsMixin` / `_SearchIndexerClientOperationsMixin`: for each `_xxx`-prefixed method that **disappeared**, remove its wrapper. For each that **gained a required parameter**, forward it through the wrapper. +- [ ] For each **new** generated method: classify it into one of the categories above. If it's a delete/create-or-update/list on a new resource type, add a wrapper. If it's a one-off, decide whether to expose it directly or wrap. +- [ ] `SearchIndexResponse` still has `.semantic` (used by `_convert_index_response`). If renamed, update the helper. +- [ ] `prefer="return=representation"` still accepted by the generated create-or-update methods. +- [ ] Apply every change to the async mirror in `indexes/aio/_operations/_patch.py`. + +--- + +### File: `azure/search/documents/indexes/aio/_operations/_patch.py` Async mirror of `indexes/_operations/_patch.py`. Same methods, all `async`. Imports `_convert_index_response` from sync — NOT duplicated. -### After Regeneration, Verify +#### After Regeneration, Verify - [ ] Same checks as sync `indexes/_operations/_patch.py` - [ ] Import of `_convert_index_response` from sync still resolves --- -## Empty `_patch.py` Files (No Customizations) - -These contain only `__all__ = []` and empty `patch_sdk()`. No action needed after regeneration. - -- `azure/search/documents/indexes/_patch.py` -- `azure/search/documents/indexes/aio/_patch.py` -- `azure/search/documents/knowledgebases/_patch.py` -- `azure/search/documents/knowledgebases/models/_patch.py` -- `azure/search/documents/knowledgebases/aio/_patch.py` +**For maintainers of this catalog:** This file describes *shape, not a snapshot*. Stable design decisions (patterns, named helpers, hand-maintained aliases) are enumerated; service-owned method rosters are described by *category* and you scan the actual `_patch.py` for the current list. Anywhere this file lists service-side methods, treat it as illustrative — the file itself is source of truth. diff --git a/sdk/search/azure-search-documents/.github/skills/azure-search-documents/references/testing.md b/sdk/search/azure-search-documents/.github/skills/azure-search-documents/references/testing.md new file mode 100644 index 000000000000..3ee1ad8fe7e2 --- /dev/null +++ b/sdk/search/azure-search-documents/.github/skills/azure-search-documents/references/testing.md @@ -0,0 +1,53 @@ +# azure-search-documents — Testing + +Tests use `pytest` plus the [Test Proxy](https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy/Azure.Sdk.Tools.TestProxy) for recording and playback. All test files live flat in `tests/`. Always create sync and async versions together. + +## Unit tests + +Pure logic. No HTTP, no recordings, no service needed. + +### File naming + +`test_.py` (sync), `test__async.py` (async). + +### Run + +```powershell +venv python -m pytest tests/ --ignore-glob="*_live*" +``` + +## Live tests + +Make HTTP calls to Azure Search. Runs in playback by default; switch to live mode to record. + +### File naming + +`test__live.py` (sync), `test__live_async.py` (async). + +### Run — playback + +Replays existing recordings. Fast, offline. + +```powershell +venv python -m pytest tests/ -k "live" +``` + +### Run — against the real service + +Captures new recordings. Required for any new live test. + +The [Test Proxy](https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy/Azure.Sdk.Tools.TestProxy) records each HTTP exchange to a separate Azure SDK assets repo. `assets.json` (in the package root) is the pointer: it stores the tag/SHA of the recordings repo this package's tests should replay. Pushing recordings updates that tag. + +Steps: + +1. First time: create `scripts\set-live-env.ps1` (gitignored — never commit; it holds endpoints and may hold connection strings) setting `SEARCH_SERVICE_ENDPOINT`, `SEARCH_SERVICE_NAME`, `SEARCH_STORAGE_CONNECTION_STRING`, `SEARCH_STORAGE_CONTAINER_NAME`, and an `az login --tenant ` line. +2. Dot-source: `. .\.github\skills\azure-search-documents\scripts\set-live-env.ps1` +3. Run with `AZURE_TEST_RUN_LIVE=true`: + ```powershell + $env:AZURE_TEST_RUN_LIVE = "true" + venv python -m pytest tests/ -k "live" + ``` + +> All `venv` invocations in this file assume the alias is set as shown in `SKILL.md → Environment Setup`. Run from the package root. +4. Push recordings: `test-proxy push -a assets.json` (updates the SHA stored in `assets.json`). +5. Include the updated `assets.json` in your PR. diff --git a/sdk/search/azure-search-documents/.github/skills/azure-search-documents/scripts/run-in-venv.ps1 b/sdk/search/azure-search-documents/.github/skills/azure-search-documents/scripts/run-in-venv.ps1 new file mode 100644 index 000000000000..ea37f85689c7 --- /dev/null +++ b/sdk/search/azure-search-documents/.github/skills/azure-search-documents/scripts/run-in-venv.ps1 @@ -0,0 +1,45 @@ +<# +.SYNOPSIS + Runs any command inside the package .venv. Self-contained — no shell + activation state required. Aborts if the .venv is missing. + +.EXAMPLE + .\.github\skills\azure-search-documents\scripts\run-in-venv.ps1 azpysdk pylint . + .\.github\skills\azure-search-documents\scripts\run-in-venv.ps1 python -m pytest tests\ + .\.github\skills\azure-search-documents\scripts\run-in-venv.ps1 pip list +#> + +if ($args.Count -eq 0) { + Write-Error "Usage: run-in-venv.ps1 [args...]" + exit 2 +} + +$pkgRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..\..\..")).Path +$venvScripts = Join-Path $pkgRoot ".venv\Scripts" + +if (-not (Test-Path (Join-Path $venvScripts "python.exe"))) { + Write-Error @" +FATAL: .venv missing at $pkgRoot\.venv. +Bootstrap it with: + cd $pkgRoot + python -m venv .venv + .\.venv\Scripts\python -m pip install -e .[dev] +Then retry. +"@ + exit 1 +} + +$env:VIRTUAL_ENV = Join-Path $pkgRoot ".venv" +$env:PATH = "$venvScripts;$env:PATH" + +Push-Location $pkgRoot +try { + if ($args.Count -eq 1) { + & $args[0] + } else { + & $args[0] @($args[1..($args.Count - 1)]) + } + exit $LASTEXITCODE +} finally { + Pop-Location +}