|
| 1 | +# |
| 2 | +# To emit from TypeSpec, run this in the current folder: |
| 3 | +# |
| 4 | +# tsp-client update --debug ==> to use the commit mentioned in the local tsp-location.yaml to generate |
| 5 | +# tsp-client update --debug --save-inputs" ==> To save the local folder TempTypeSpecFiles |
| 6 | +# tsp-client update --debug --local-spec-repo <path>" ==> to use your local TypeSpec folder. Path is like: |
| 7 | +# D:\src\azure-rest-api-specs\specification\ai-foundry\data-plane\Foundry\src\sdk-python-js-azure-ai-projects |
| 8 | +# |
| 9 | +# Then run this script to "fix" the emitted code: |
| 10 | +# powershell -ExecutionPolicy Bypass -File PostEmitter.ps1 |
| 11 | +# |
| 12 | + |
| 13 | +# Revert emitted pyprojects.toml, since it overrides the following changes: |
| 14 | +# - We added "Programming Language :: Python :: 3.14". The emitter removes it. |
| 15 | +# - The emitter uses lower case "i" in "Ai". I want to keep it upper case in the description field: "Microsoft Corporation Azure AI Projects Client Library for Python". |
| 16 | +# - We want a vanity link for the "repository" value, deep linking to the SDK folder (not root of repo): https://aka.ms/azsdk/azure-ai-projects-v2/python/code |
| 17 | +git restore pyproject.toml |
| 18 | + |
| 19 | + |
| 20 | +# Edit both _operations.py files to fix missing Foundry-Features HTTP request header in continued list paging calls. Add: |
| 21 | +# headers=_headers |
| 22 | +# to the end of each of these lines in the BetaXxxOperations classes (do not do this in GA operations classes!) |
| 23 | +# "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params |
| 24 | +# In emitted code, these first 7 of those lines are associated with GA operations, so start the replacement |
| 25 | +# from the 8th occurrence onward. |
| 26 | +$gaCount = 7 |
| 27 | +$old = [char]34 + 'GET' + [char]34 + ', urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params' |
| 28 | +$new = $old + ', headers=_headers' |
| 29 | +foreach ($f in 'azure\ai\projects\aio\operations\_operations.py', 'azure\ai\projects\operations\_operations.py') { |
| 30 | + $c = Get-Content $f -Raw |
| 31 | + $parts = $c -split [regex]::Escape($old) |
| 32 | + $r = $parts[0] |
| 33 | + for ($i = 1; $i -lt $parts.Length; $i++) { |
| 34 | + if ($i -le $gaCount) { |
| 35 | + $r += $old + $parts[$i] |
| 36 | + } else { |
| 37 | + $r += $new + $parts[$i] |
| 38 | + } |
| 39 | + } |
| 40 | + Set-Content $f $r -NoNewline |
| 41 | +} |
| 42 | + |
| 43 | +# Force streaming in get_session_log_stream for both sync and async operations. |
| 44 | +$files = 'azure\ai\projects\operations\_operations.py', 'azure\ai\projects\aio\operations\_operations.py' |
| 45 | +foreach ($f in $files) { |
| 46 | + $lines = Get-Content $f |
| 47 | + $inFunc = $false |
| 48 | + for ($i = 0; $i -lt $lines.Length; $i++) { |
| 49 | + if ($lines[$i] -match '^\s*(async\s+)?def\s+get_session_log_stream\(') { |
| 50 | + $inFunc = $true |
| 51 | + continue |
| 52 | + } |
| 53 | + if ($inFunc -and $lines[$i] -match '^\s*(async\s+)?def\s+\w+\(') { |
| 54 | + $inFunc = $false |
| 55 | + } |
| 56 | + if ($inFunc -and $lines[$i] -match 'kwargs\.pop\(.+stream.+False\)') { |
| 57 | + $indent = ([regex]::Match($lines[$i], '^\s*')).Value |
| 58 | + $lines[$i] = $indent + '_stream = True' |
| 59 | + } |
| 60 | + } |
| 61 | + Set-Content $f $lines |
| 62 | +} |
| 63 | + |
| 64 | +# Fix Sphinx issue in class ToolChoiceAllowed, in "tools" property doc string. The "Required" cannot come at the end of the code-block. |
| 65 | +# move it to the end of the text before the code block, and make sure there are no periods after "]". |
| 66 | +# .. code-block:: json |
| 67 | +# |
| 68 | +# [ |
| 69 | +# { "type": "function", "name": "get_weather" }, |
| 70 | +# { "type": "mcp", "server_label": "deepwiki" }, |
| 71 | +# { "type": "image_generation" } |
| 72 | +# ]. Required. |
| 73 | +(Get-Content azure\ai\projects\models\_models.py) -replace 'Responses API, the list of tool definitions might look like:', 'Responses API, the list of tool definitions might look like the following. Required.' | Set-Content azure\ai\projects\models\_models.py |
| 74 | +(Get-Content azure\ai\projects\models\_models.py) -replace 'list of tool definitions might look like:', 'list of tool definitions might look like the following. Required.' | Set-Content azure\ai\projects\models\_models.py |
| 75 | +(Get-Content azure\ai\projects\models\_models.py) -replace ' \]\. Required\.', ' ]' | Set-Content azure\ai\projects\models\_models.py |
| 76 | + |
| 77 | +# Fix Sphinx docutils warnings in class SessionLogEvent: the generated docstring wraps two long |
| 78 | +# ``data:`` JSON lines mid-string inside a ``.. code-block::`` section. The wrapped continuation |
| 79 | +# lines have wrong indentation (4 spaces instead of 7), causing "unexpected unindent" warnings. |
| 80 | +# Join each broken pair back into one line. |
| 81 | +$f = 'azure\ai\projects\models\_models.py' |
| 82 | +$c = Get-Content $f -Raw |
| 83 | +$c = $c -replace '(Starting server)\r?\n[ \t]+(on port 18080)', '$1 $2' |
| 84 | +$c = $c -replace '(Successfully)\r?\n[ \t]+(connected to container\"})\.?', '$1 $2' |
| 85 | +Set-Content $f $c -NoNewline |
| 86 | +$lines = Get-Content $f |
| 87 | +$out = @() |
| 88 | +foreach ($line in $lines) { |
| 89 | + if ($line -match '^\s*on port 18080' -and $line -notmatch 'data:') { continue } |
| 90 | + if ($line -match '^\s*connected to container' -and $line -notmatch 'data:') { continue } |
| 91 | + if ($line -match '^\s*data: .*2026-03-10T09:33:17.121Z') { |
| 92 | + $out += (' ' + $line.TrimStart()) |
| 93 | + continue |
| 94 | + } |
| 95 | + if ($line -match '^\s*data: .*2026-03-10T09:34:52.714Z') { |
| 96 | + $out += (' ' + $line.TrimStart()) |
| 97 | + continue |
| 98 | + } |
| 99 | + $out += $line |
| 100 | +} |
| 101 | +Set-Content $f $out |
| 102 | + |
| 103 | +# Fix Sphinx docutils warnings in get_session_log_stream docstrings (sync + async). |
| 104 | +# The emitter wraps bullet/code-block lines with insufficient indentation. |
| 105 | +$files = 'azure\ai\projects\operations\_operations.py', 'azure\ai\projects\aio\operations\_operations.py' |
| 106 | +foreach ($f in $files) { |
| 107 | + $c = Get-Content $f -Raw |
| 108 | + $c = $c -replace 'schema\r?\n\s+is not contractual and may include additional keys or change format\r?\n\s+over time [^\r\n]*clients should treat it as an opaque string\)', 'schema is not contractual and may include additional keys or change format over time; clients should treat it as an opaque string)' |
| 109 | + $c = $c -replace '(message\":\"Starting)\r?\n\s+(FoundryCBAgent server on port 8088\"})', '$1 $2' |
| 110 | + $c = $c -replace '(message\":\"INFO: Application)\r?\n\s+(startup complete\.\"})', '$1 $2' |
| 111 | + $c = $c -replace '(message\":\"Successfully)\r?\n\s+(connected to container\"})', '$1 $2' |
| 112 | + $c = $c -replace '(message\":\"No logs since)\r?\n\s+(last 60 seconds\"})', '$1 $2' |
| 113 | + Set-Content $f $c -NoNewline |
| 114 | +} |
| 115 | + |
| 116 | +# A block of code in the implementation of "list_memories", in both sync |
| 117 | +# and async _operations.py files, needs to be moved up. It's emitted in the wrong place, |
| 118 | +# in the inline function named "prepare_request". Instead it should be moved up into the |
| 119 | +# main body of the "list_memories" method, right after the line `error_map.update(kwargs.pop("error_map", {}) or {})`. |
| 120 | +# If you don't do this, the PR pipeline will show failures in Pyright (`error: "body" is unbound (reportUnboundVariable)`) |
| 121 | +# and some tests will fail. This is the block of code that needs to move up: |
| 122 | +# if body is _Unset: |
| 123 | +# if scope is _Unset: |
| 124 | +# raise TypeError("missing required argument: scope") |
| 125 | +# body = {"scope": scope} |
| 126 | +# body = {k: v for k, v in body.items() if v is not None} |
| 127 | +# The block inside prepare_request has 12-space indentation; after moving to the main function body it needs 8-space indentation. |
| 128 | +# Strategy: Find the last list_memories method, then do a targeted string replacement that moves the block right after error_map.update. |
| 129 | +$oldPattern = @" |
| 130 | + error_map.update(kwargs.pop("error_map", {}) or {}) |
| 131 | + content_type = content_type or "application/json" |
| 132 | + _content = None |
| 133 | + if isinstance(body, (IOBase, bytes)): |
| 134 | + _content = body |
| 135 | + else: |
| 136 | + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore |
| 137 | +
|
| 138 | + def prepare_request(_continuation_token=None): |
| 139 | + if body is _Unset: |
| 140 | + if scope is _Unset: |
| 141 | + raise TypeError("missing required argument: scope") |
| 142 | + body = {"scope": scope} |
| 143 | + body = {k: v for k, v in body.items() if v is not None} |
| 144 | +
|
| 145 | + _request = build_beta_memory_stores_list_memories_request( |
| 146 | +"@ |
| 147 | +$newPattern = @" |
| 148 | + error_map.update(kwargs.pop("error_map", {}) or {}) |
| 149 | + if body is _Unset: |
| 150 | + if scope is _Unset: |
| 151 | + raise TypeError("missing required argument: scope") |
| 152 | + body = {"scope": scope} |
| 153 | + body = {k: v for k, v in body.items() if v is not None} |
| 154 | + content_type = content_type or "application/json" |
| 155 | + _content = None |
| 156 | + if isinstance(body, (IOBase, bytes)): |
| 157 | + _content = body |
| 158 | + else: |
| 159 | + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore |
| 160 | +
|
| 161 | + def prepare_request(_continuation_token=None): |
| 162 | + _request = build_beta_memory_stores_list_memories_request( |
| 163 | +"@ |
| 164 | +$files = 'azure\ai\projects\operations\_operations.py', 'azure\ai\projects\aio\operations\_operations.py' |
| 165 | +foreach ($f in $files) { |
| 166 | + $c = Get-Content $f -Raw |
| 167 | + # Find all occurrences of "def list_memories(" and get the index of the last one |
| 168 | + $methodMatches = [regex]::Matches($c, 'def list_memories\(') |
| 169 | + if ($methodMatches.Count -eq 0) { continue } |
| 170 | + $lastMethodStart = $methodMatches[$methodMatches.Count - 1].Index |
| 171 | + |
| 172 | + # Find the pattern to replace - first occurrence after the last list_memories method |
| 173 | + $patternEscaped = [regex]::Escape($oldPattern) |
| 174 | + $patternMatches = [regex]::Matches($c, $patternEscaped) |
| 175 | + $matchToReplace = $null |
| 176 | + foreach ($m in $patternMatches) { |
| 177 | + if ($m.Index -gt $lastMethodStart) { |
| 178 | + $matchToReplace = $m |
| 179 | + break |
| 180 | + } |
| 181 | + } |
| 182 | + if ($matchToReplace -eq $null) { continue } |
| 183 | + |
| 184 | + # Replace only that specific occurrence |
| 185 | + $c = $c.Substring(0, $matchToReplace.Index) + $newPattern + $c.Substring($matchToReplace.Index + $matchToReplace.Length) |
| 186 | + |
| 187 | + Set-Content $f $c -NoNewline |
| 188 | +} |
| 189 | + |
| 190 | + |
| 191 | +# Finishing by running 'black' tool to format code. |
| 192 | +pip install black |
| 193 | +black --config ../../../eng/black-pyproject.toml . |
0 commit comments