Skip to content

Commit 408e178

Browse files
committed
fix(ci): clear mypy gate — types-requests stubs + latent create_folder bug
The 2026-05-29 issues-fixes commit broke the CI mypy step (10 errors): - Add types-requests to requirements-dev.txt (7 missing-stub errors) - Fix copy_folder calling create_folder with a dict instead of (name, parent_folder_uuid, ...); update the masking test mock - Annotate all_items: list in cli.py - restore_item destination_folder_uuid: str -> Optional[str] All gates green: ruff, mypy, bandit, 591 unit + 31 live tests pass. Docs (HISTORY/LEARNINGS/readme) updated.
1 parent f5bf51c commit 408e178

8 files changed

Lines changed: 56 additions & 17 deletions

File tree

HISTORY.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,33 @@ retrospective lessons see [`LEARNINGS.md`](LEARNINGS.md).
66

77
---
88

9+
## CI mypy-gate fix (2026-05-30)
10+
11+
The 2026-05-29 issues-fixes commit (`f5bf51c`) was green locally but the CI
12+
**Type check (mypy)** step fast-failed (24s) with 10 errors. The earlier
13+
local runs hadn't caught them because of a split toolchain (homebrew mypy /
14+
conda python / framework pip) where the freshly-installed stubs landed in a
15+
different interpreter than the one mypy actually used. Fixes:
16+
17+
- **`types-requests` missing.** mypy reported "Library stubs not installed
18+
for requests" at 7 sites. Added `types-requests>=2.31.0` to
19+
`requirements-dev.txt`. CI installs everything into one venv, so the
20+
requirements line is sufficient there.
21+
- **Latent `create_folder` signature bug.** `copy_folder` (`drive.py`) called
22+
`self.create_folder(payload_dict)`, but `DriveService.create_folder` takes
23+
`(name, parent_folder_uuid, ...)` — the dict was being passed as `name`.
24+
The unit test masked it with a `fake_create(payload)` mock, so only mypy's
25+
`arg-type` error flagged it. Fixed the call to pass named args and updated
26+
the test mock to match the real signature.
27+
- **Two minor type errors:** annotated `all_items: list` in `cli.py` and
28+
changed `restore_item`'s `destination_folder_uuid: str = None` to
29+
`Optional[str]` in `utils/api.py`.
30+
31+
All four gates (ruff, mypy, bandit, pytest) pass again. Test count:
32+
**591 unit + 31 live = 622**, re-verified against live credentials.
33+
34+
---
35+
936
## GitHub Issues Fixes + Feature Work (2026-05-29)
1037

1138
Two rounds of work addressing all 6 open GitHub issues plus feature

LEARNINGS.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,19 @@ backend does. Integration tests verify what the backend actually
6060
does.** Both are needed. Unit tests for fast iteration on code logic;
6161
live tests as a contract check.
6262

63+
### A mock can hide a real signature mismatch — mypy won't
64+
65+
`copy_folder` called `self.create_folder(payload_dict)` while the real
66+
`DriveService.create_folder(name, parent_folder_uuid, ...)` expects a
67+
string. The test passed for months because its `fake_create(payload)`
68+
mock accepted exactly the wrong shape the production code was sending —
69+
the mock was written against the bug, not against the signature. mypy's
70+
`arg-type` error was the only thing that caught it. Lesson: **when a test
71+
mocks a method, mock its real signature, not the call site you happen to
72+
have.** And keep the type gate green in CI, not just locally — a split
73+
toolchain (mypy and pip pointing at different interpreters) silently
74+
masked it on the dev machine.
75+
6376
---
6477

6578
## On the trust roots

cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2016,7 +2016,7 @@ def trash_list(item_type: str, limit: int, as_json: bool):
20162016
auth_service.get_auth_details()
20172017

20182018
offset = 0
2019-
all_items = []
2019+
all_items: list = []
20202020
while True:
20212021
page_size = min(limit - len(all_items), 50)
20222022
result = api_client.get_trash_content(offset=offset, limit=page_size,

readme.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ internxt-python/
270270
│ └── webdav_server.py # WebDAV server management
271271
├── utils/
272272
│ └── api.py # HTTP API client
273-
├── tests/ # Pytest suite (~560 tests, 90% coverage)
273+
├── tests/ # Pytest suite (622 tests, 90% coverage)
274274
├── pyproject.toml # Pytest, coverage, ruff config
275275
├── requirements-dev.txt # Dev/test dependencies
276276
└── .github/workflows/ci.yml # Lint + type-check + test on Py 3.10/3.11/3.12
@@ -294,7 +294,8 @@ pip install -r requirements-dev.txt
294294

295295
### Running Tests
296296

297-
The project ships with a 557-test pytest suite at **90% line coverage**.
297+
The project ships with a 591-test unit suite at **90% line coverage**
298+
(plus 31 optional live tests — 622 total).
298299
It covers crypto round-trips, path resolution, upload/download conflict
299300
handling, the WebDAV provider, all major CLI commands, and a real
300301
encrypt → upload → "wire" → download → decrypt round-trip cycle.
@@ -337,7 +338,7 @@ Per-module coverage:
337338

338339
#### Live integration smoke (optional)
339340

340-
`tests/test_live_smoke.py` is a 22-test suite that runs end-to-end
341+
`tests/test_live_smoke.py` is a 31-test suite that runs end-to-end
341342
against the real Internxt backend, covering: login, list, upload (small
342343
+ unicode + extensionless + 2 MB), download with byte-for-byte
343344
verification, recursive folder creation, file rename/move/copy/update,

requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pytest>=7.4.0
44
pytest-cov>=4.1.0
55
ruff>=0.1.0
66
mypy>=1.5.0
7+
types-requests>=2.31.0
78
bandit>=1.7.0
89

910
# Optional: load IXT_ACCOUNT/IXT_PWD from .env for the live smoke test

services/drive.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -746,16 +746,12 @@ def copy_folder(self, source_folder_uuid: str, destination_parent_uuid: str,
746746
modification_time = source_meta.get('modificationTime') or source_meta.get('updatedAt')
747747

748748
# Create destination folder
749-
payload: Dict[str, Any] = {
750-
'plainName': name,
751-
'parentFolderUuid': destination_parent_uuid,
752-
}
753-
if creation_time:
754-
payload['creationTime'] = creation_time
755-
if modification_time:
756-
payload['modificationTime'] = modification_time
757-
758-
new_folder = self.create_folder(payload)
749+
new_folder = self.create_folder(
750+
name,
751+
destination_parent_uuid,
752+
creation_time=creation_time,
753+
modification_time=modification_time,
754+
)
759755
new_folder_uuid = new_folder['uuid']
760756
print(f" 📁 Created folder: {name}{new_folder_uuid}")
761757

tests/test_drive_misc.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,10 @@ def fake_get_meta(uuid):
246246
call_count['get_folder_metadata'] += 1
247247
return fake_folder_meta if uuid == 'src-uuid' else fake_sub_meta
248248

249-
def fake_create(payload):
249+
def fake_create(name, parent_folder_uuid=None, creation_time=None,
250+
modification_time=None):
250251
call_count['create_folder'] += 1
251-
if payload['plainName'] == 'Src':
252+
if name == 'Src':
252253
return fake_new_folder
253254
return fake_sub_folder
254255

utils/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ def clear_trash(self) -> Dict[str, Any]:
351351
url = f"{self.drive_api_url}/storage/trash/all"
352352
return self.delete(url)
353353

354-
def restore_item(self, item_uuid: str, item_type: str, destination_folder_uuid: str = None) -> Dict[str, Any]:
354+
def restore_item(self, item_uuid: str, item_type: str, destination_folder_uuid: Optional[str] = None) -> Dict[str, Any]:
355355
"""
356356
Restore item from trash - keeping original implementation
357357
(Not clearly shown in provided SDK snippets)

0 commit comments

Comments
 (0)