|
| 1 | +# Python SDK Validation Report |
| 2 | + |
| 3 | +**Branch:** `next` (at `9707100e1`) |
| 4 | +**Date:** 2026-04-24 |
| 5 | +**Checkers:** pyright 1.1.399, mypy 1.17, ruff |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## Summary |
| 10 | + |
| 11 | +| Check | Result | |
| 12 | +|---------|--------| |
| 13 | +| ruff | PASS | |
| 14 | +| pyright | FAIL - 55 errors | |
| 15 | +| mypy | FAIL - 12 errors | |
| 16 | + |
| 17 | +All failures are type-checking errors. There are **4 distinct issues** across 6 source files (+1 test file), |
| 18 | +producing the full error count due to cascading type-unknown propagation. |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## Issue 1: Missing `Required` import in `asset_create_params.py` |
| 23 | + |
| 24 | +**Files:** `src/cloudflare/types/ai/finetunes/asset_create_params.py` |
| 25 | +**Error count:** 3 (pyright) + 3 (mypy) |
| 26 | +**Error type:** `reportUndefinedVariable` / `name-defined` |
| 27 | + |
| 28 | +### What's wrong |
| 29 | + |
| 30 | +`Required` is used on lines 13, 15, and 18 but is not imported. The import line reads: |
| 31 | + |
| 32 | +```python |
| 33 | +from typing_extensions import TypedDict |
| 34 | +``` |
| 35 | + |
| 36 | +It should be: |
| 37 | + |
| 38 | +```python |
| 39 | +from typing_extensions import Required, TypedDict |
| 40 | +``` |
| 41 | + |
| 42 | +### Change history |
| 43 | + |
| 44 | +| Date | Commit | What happened | |
| 45 | +|------------|-------------|---------------| |
| 46 | +| 2025-01-02 | `4493d6c28` | File created with `Required` import and `Required[str]` on `account_id` | |
| 47 | +| 2026-04-19 | `3b83baa3b` | Codegen removed `Required` import AND `Required` usage (made fields optional) | |
| 48 | +| 2026-04-23 | `988df8632` | Codegen re-added `Required` to 3 fields BUT did not restore the import | |
| 49 | + |
| 50 | +### Root cause |
| 51 | + |
| 52 | +The `988df8632` commit ("remove account_id and zone_id client options") re-promoted `account_id`, `file`, and |
| 53 | +`file_name` back to `Required` status but the codegen output omitted the corresponding |
| 54 | +`from typing_extensions import Required` update. This is a Stainless codegen bug -- it emitted `Required` usage |
| 55 | +without the import. |
| 56 | + |
| 57 | +### Fix |
| 58 | + |
| 59 | +Add `Required` to the import: |
| 60 | + |
| 61 | +```python |
| 62 | +from typing_extensions import Required, TypedDict |
| 63 | +``` |
| 64 | + |
| 65 | +--- |
| 66 | + |
| 67 | +## Issue 2: `SchemaFieldStruct` and `SchemaFieldList` missing `TypedDict` base class |
| 68 | + |
| 69 | +**Files:** |
| 70 | +- `src/cloudflare/types/pipelines/sink_create_params.py` (lines 293, 297) |
| 71 | +- `src/cloudflare/types/pipelines/stream_create_params.py` (lines 201, 205) |
| 72 | + |
| 73 | +**Error count:** 4 (pyright) + 4 (mypy) |
| 74 | +**Error type:** `reportGeneralTypeIssues` + `reportCallIssue` / `call-arg` |
| 75 | + |
| 76 | +### What's wrong |
| 77 | + |
| 78 | +Both classes are declared as: |
| 79 | + |
| 80 | +```python |
| 81 | +class SchemaFieldStruct(total=False): |
| 82 | + pass |
| 83 | + |
| 84 | +class SchemaFieldList(total=False): |
| 85 | + pass |
| 86 | +``` |
| 87 | + |
| 88 | +They should inherit from `TypedDict`: |
| 89 | + |
| 90 | +```python |
| 91 | +class SchemaFieldStruct(TypedDict, total=False): |
| 92 | + ... |
| 93 | + |
| 94 | +class SchemaFieldList(TypedDict, total=False): |
| 95 | + ... |
| 96 | +``` |
| 97 | + |
| 98 | +Without `TypedDict`, these are plain class definitions. `total=False` is passed to |
| 99 | +`object.__init_subclass__()` which does not accept that keyword argument. |
| 100 | + |
| 101 | +### Change history |
| 102 | + |
| 103 | +| Date | Commit | What happened | |
| 104 | +|------------|-------------|---------------| |
| 105 | +| 2025-11-12 | `008556f6a` | Pipelines feature introduced these types (generated with stub bodies, had pyright ignores) | |
| 106 | +| 2026-02-11 | `1c415a2dd` | **Manual fix**: added `TypedDict` base class, proper `type`, `name`, `fields`/`element` fields | |
| 107 | +| 2026-04-19 | `3b83baa3b` | Codegen overwrote the manual fix, regenerating stubs without `TypedDict` base class | |
| 108 | + |
| 109 | +### Root cause |
| 110 | + |
| 111 | +Stainless codegen is emitting empty stub classes for the `struct` and `list` schema field variants. The OpenAPI |
| 112 | +spec likely defines these as recursive types (struct has `fields: [SchemaField]`, list has `element: SchemaField`) |
| 113 | +which the codegen can't fully resolve, so it emits empty stubs. A previous manual fix (`1c415a2dd`) was |
| 114 | +overwritten by subsequent codegen runs. |
| 115 | + |
| 116 | +### Fix |
| 117 | + |
| 118 | +Restore the `TypedDict` base class and proper fields as done in `1c415a2dd`. For both |
| 119 | +`sink_create_params.py` and `stream_create_params.py`: |
| 120 | + |
| 121 | +```python |
| 122 | +class SchemaFieldStruct(TypedDict, total=False): |
| 123 | + type: Required[Literal["struct"]] |
| 124 | + metadata_key: Optional[str] |
| 125 | + name: str |
| 126 | + required: bool |
| 127 | + sql_name: str |
| 128 | + fields: Optional[List["SchemaField"]] |
| 129 | + |
| 130 | +class SchemaFieldList(TypedDict, total=False): |
| 131 | + type: Required[Literal["list"]] |
| 132 | + metadata_key: Optional[str] |
| 133 | + name: str |
| 134 | + required: bool |
| 135 | + sql_name: str |
| 136 | + element: Optional["SchemaField"] |
| 137 | +``` |
| 138 | + |
| 139 | +This fix will be overwritten by the next codegen run unless the Stainless config or upstream OpenAPI spec is also |
| 140 | +fixed to properly model these recursive types. |
| 141 | + |
| 142 | +--- |
| 143 | + |
| 144 | +## Issue 3: Missing `organization_profile_get_params` module |
| 145 | + |
| 146 | +**Files:** |
| 147 | +- `src/cloudflare/resources/organizations/organization_profile.py` (line 22) |
| 148 | +- `tests/api_resources/organizations/test_organization_profile.py` (line 12) |
| 149 | + |
| 150 | +**Error count:** 22 (pyright) + 1 (mypy) -- most pyright errors are cascading `Unknown` type propagation |
| 151 | +**Error type:** `reportMissingImports` / `import-not-found` |
| 152 | + |
| 153 | +### What's wrong |
| 154 | + |
| 155 | +Both the resource module and its test file import: |
| 156 | + |
| 157 | +```python |
| 158 | +from ...types.organizations.organization_profile_get_params import Result |
| 159 | +``` |
| 160 | + |
| 161 | +But the file `organization_profile_get_params.py` does not exist. It was deleted by codegen. |
| 162 | + |
| 163 | +### Change history |
| 164 | + |
| 165 | +| Date | Commit | What happened | |
| 166 | +|------------|-------------|---------------| |
| 167 | +| 2026-02-11 | `1c415a2dd` | **Manual fix**: created `organization_profile_get_params.py` with `Result = OrganizationProfile` | |
| 168 | +| 2026-04-19 | `3b83baa3b` | Codegen created `organization_profile.py` resource file, importing `Result` from get_params | |
| 169 | +| 2026-04-22 | `0d6464258` | Codegen **deleted** `organization_profile_get_params.py` and removed it from `__init__.py` | |
| 170 | +| 2026-04-23 | `988df8632` | Resource file unchanged -- still imports from deleted module | |
| 171 | + |
| 172 | +### Root cause |
| 173 | + |
| 174 | +There is a sequencing inconsistency in the codegen output. The resource file (`organization_profile.py`) was |
| 175 | +generated expecting a `Result` type alias from a params file, but a later codegen run deleted that params file |
| 176 | +without updating the resource file's import. The `Result` type was just an alias for `OrganizationProfile`. |
| 177 | + |
| 178 | +### Fix |
| 179 | + |
| 180 | +Option A -- Recreate the deleted file: |
| 181 | + |
| 182 | +Create `src/cloudflare/types/organizations/organization_profile_get_params.py`: |
| 183 | +```python |
| 184 | +from __future__ import annotations |
| 185 | +from typing_extensions import TypeAlias |
| 186 | +from .organization_profile import OrganizationProfile |
| 187 | + |
| 188 | +__all__ = ["Result"] |
| 189 | + |
| 190 | +Result: TypeAlias = OrganizationProfile |
| 191 | +``` |
| 192 | + |
| 193 | +And add to `src/cloudflare/types/organizations/__init__.py`: |
| 194 | +```python |
| 195 | +from .organization_profile import OrganizationProfile as OrganizationProfile |
| 196 | +``` |
| 197 | + |
| 198 | +Option B -- Inline the import (smaller diff): |
| 199 | + |
| 200 | +In `organization_profile.py` and the test file, replace: |
| 201 | +```python |
| 202 | +from ...types.organizations.organization_profile_get_params import Result |
| 203 | +``` |
| 204 | +with: |
| 205 | +```python |
| 206 | +from ...types.organizations.organization_profile import OrganizationProfile as Result |
| 207 | +``` |
| 208 | + |
| 209 | +Option A is preferred since it matches the pattern codegen expects and is more durable. |
| 210 | + |
| 211 | +--- |
| 212 | + |
| 213 | +## Issue 4: `_get_api_list` called with unsupported `files` parameter |
| 214 | + |
| 215 | +**Files:** |
| 216 | +- `src/cloudflare/resources/ai/to_markdown.py` (lines 118, 217) |
| 217 | + |
| 218 | +**Error count:** 4 (pyright) + 2 (mypy) (2 calls x sync/async variants) |
| 219 | +**Error type:** `reportCallIssue` + `reportUnknownVariableType` / `call-arg` |
| 220 | + |
| 221 | +### What's wrong |
| 222 | + |
| 223 | +The `transform` method passes `files=files` to `self._get_api_list()`: |
| 224 | + |
| 225 | +```python |
| 226 | +return self._get_api_list( |
| 227 | + ..., |
| 228 | + body=maybe_transform(body, ...), |
| 229 | + files=files, # <-- not in get_api_list signature |
| 230 | + options=..., |
| 231 | + model=..., |
| 232 | + method="post", |
| 233 | +) |
| 234 | +``` |
| 235 | + |
| 236 | +The `get_api_list` method signature in `_base_client.py` is: |
| 237 | + |
| 238 | +```python |
| 239 | +def get_api_list(self, path, *, model, page, body=None, options={}, method="get") -> SyncPageT |
| 240 | +``` |
| 241 | + |
| 242 | +There is no `files` parameter. The kwargs likely pass through to the underlying request options via `**options`, |
| 243 | +but the type signature rejects it. |
| 244 | + |
| 245 | +### Change history |
| 246 | + |
| 247 | +| Date | Commit | What happened | |
| 248 | +|------------|-------------|---------------| |
| 249 | +| 2026-02-11 | `f280942f4` | Added `# pyright: ignore` / `# type: ignore` to the **same pattern** in `radar/ai/to_markdown.py` | |
| 250 | +| 2026-04-19 | `3b83baa3b` | Codegen created the new `ai/to_markdown.py` with same `files=` pattern but **without** suppression comments | |
| 251 | + |
| 252 | +### Root cause |
| 253 | + |
| 254 | +This is a known Stainless codegen limitation: `_get_api_list` does not accept `files` in its typed signature, but |
| 255 | +multipart file upload endpoints that also return paginated lists need to pass files through. The older |
| 256 | +`radar/ai/to_markdown.py` has manual suppression comments that survive codegen. The newly generated |
| 257 | +`ai/to_markdown.py` was created fresh and lacks them. |
| 258 | + |
| 259 | +### Fix |
| 260 | + |
| 261 | +Add type-checker suppression comments matching the pattern in `radar/ai/to_markdown.py`: |
| 262 | + |
| 263 | +```python |
| 264 | +files=files, # pyright: ignore[reportCallIssue] # type: ignore[call-arg] |
| 265 | +``` |
| 266 | + |
| 267 | +On both line 118 (sync) and line 217 (async) in `src/cloudflare/resources/ai/to_markdown.py`. |
| 268 | + |
| 269 | +--- |
| 270 | + |
| 271 | +## Cross-cutting observations |
| 272 | + |
| 273 | +1. **Manual fixes are fragile.** Issues 2, 3, and 4 all stem from manual fixes (commits `1c415a2dd` and |
| 274 | + `f280942f4`) being overwritten by subsequent Stainless codegen runs. Any fix applied here will face the same |
| 275 | + risk on the next codegen sync unless the upstream Stainless config or OpenAPI spec is also corrected. |
| 276 | + |
| 277 | +2. **Upstream Stainless bugs to track:** |
| 278 | + - Missing `Required` import when re-promoting fields (Issue 1) |
| 279 | + - Empty stub classes for recursive TypedDict types in pipelines (Issue 2) |
| 280 | + - Inconsistent file deletion without updating dependents (Issue 3) |
| 281 | + - Missing `files` parameter in `_get_api_list` signature for multipart paginated endpoints (Issue 4) |
| 282 | + |
| 283 | +3. **Recommended durable fixes:** |
| 284 | + - Issues 1, 3, 4: File Stainless bug reports so codegen output is correct |
| 285 | + - Issue 2: Fix the OpenAPI spec for pipelines to properly model recursive struct/list types, or add Stainless |
| 286 | + config overrides to emit correct TypedDict stubs |
0 commit comments