Skip to content

[Typing] Add Python type annotations across codebase (Part 1: Mechanical) - #5424

Open
letitz wants to merge 17 commits into
google:masterfrom
letitz:typing-mechanical
Open

[Typing] Add Python type annotations across codebase (Part 1: Mechanical)#5424
letitz wants to merge 17 commits into
google:masterfrom
letitz:typing-mechanical

Conversation

@letitz

@letitz letitz commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Overview

This is Part 1 of the static typing rollout for ClusterFuzz. It introduces comprehensive Python type annotations across all files in src/appengine and src/clusterfuzz with zero runtime behavioral changes.

Part 2 (stacked on top of this PR) contains targeted runtime fixes and defensive checks identified during the typing process.

Summary of Changes

  • Codebase-Wide Type Annotations: Added type annotations across 313 Python modules in src/appengine, src/clusterfuzz, and src/local.
  • NDB Type Stubs: Implemented comprehensive type stub definitions for google-cloud-ndb in typings/google/cloud/ndb/, including:
    • Strongly typed Model, Expando, Key, and Query interfaces.
    • Keyword-only repeated=True overloads across property descriptors (StringProperty, IntegerProperty, LocalStructuredProperty, etc.) ensuring repeated properties resolve directly to list[T].
    • Descriptor __get__ overloads supporting instance vs class-level attribute access and Datastore comparison operators (<=, >=, ==, .IN()).
  • Configuration & Strict Dual Type Checking: Enabled type checking in pyproject.toml and pyrightconfig.json covering all codebase modules.
  • Obsolete Cast Cleanup: Removed legacy cast(Any, ...) calls on NDB queries and model entities that predated custom stubs.

Verification & Safety

  • Type Checkers:
    • uv run ty check: 0 errors (100% clean across all modules)
    • uv run butler.py lint --type-check: 0 errors, 0 warnings
  • Unit Test Suite:
    • python butler.py py_unittest -t core -m: 2,221 tests passed (0 errors, 0 failures)
    • python butler.py py_unittest -t appengine -m: 742 tests passed (0 errors, 0 failures)
  • Runtime Invariance: All functional code logic, assertions, and execution paths remain strictly identical to previous runtime behavior (with type ignores/casts used where runtime changes are deferred to Part 2).

@letitz letitz left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A quick review of the first few files shows that there are several changes here that modify runtime behavior and should not be in part 1 but rather in part 2.

predator_result: Optional[dict[str, Any]]) -> Optional[dict[str, Any]]:
"""Parse raw suspected_cls into dict."""
if not predator_result:
if not predator_result or 'result' not in predator_result:

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a mechanical change, it should go into part 2.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted to mechanical form with predator_result = cast(dict[str, Any], predator_result['result']). The defensive check is kept in Part 2.

# with the response.
predator_result = predator_result['result']
result = predator_result['result']
if not isinstance(result, dict):

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted in Part 1 and deferred to Part 2.

filtered_crash_lines = []
linkifier = source_mapper.StackFrameLinkifier(revisions_dict)
filtered_crash_lines: list[str] = []
linkifier = source_mapper.StackFrameLinkifier(revisions_dict or {})

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this is not a mechanical change.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted to linkifier = source_mapper.StackFrameLinkifier(revisions_dict) # type: ignore in Part 1. The revisions_dict or {} fallback is deferred to Part 2.

self.important)

def __eq__(self, other):
def __eq__(self, other: object) -> bool:

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

other should have type Line.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated other parameter to type 'Line'.


frames = get_stack_frames(crash_state_lines)
escaped_frames = [markupsafe.escape(f) for f in frames]
escaped_frames = [str(markupsafe.escape(f)) for f in frames]

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also a runtime change.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted str(...) wrapper to [markupsafe.escape(f) for f in frames].

@letitz
letitz force-pushed the typing-mechanical branch 6 times, most recently from 4d23846 to 33ca4c4 Compare August 13, 2026 22:07
Comment thread src/clusterfuzz/_internal/base/bisection.py Outdated
Comment thread src/clusterfuzz/_internal/base/bisection.py Outdated
Comment thread src/clusterfuzz/_internal/base/dates.py Outdated
Comment thread src/clusterfuzz/_internal/base/errors.py Outdated

testcase = data_handler.get_testcase_by_id(testcase_id)
fuzz_target = testcase.get_fuzz_target()
fuzz_target = cast(data_types.FuzzTarget, testcase.get_fuzz_target())

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need this cast now that ndb models have better types?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the cast; using fuzz_target = testcase.get_fuzz_target() with assert fuzz_target is not None.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding an assert changes runtime behavior, we should not do that.

@letitz
letitz force-pushed the typing-mechanical branch 5 times, most recently from 623457d to 6571867 Compare August 14, 2026 03:46
letitz added 15 commits August 14, 2026 15:48
This commit completes the migration of ClusterFuzz's Python dependency
management system from Pipenv to uv. pipx is used to install uv locally.

Architecture & Configs:
- Created root pyproject.toml with 5 dependency groups, centralizing
  dependency definitions in a single file (from two Pipfiles) and removing
  the need for the hack where appengine packages were modelled as dev
  dependencies in src/Pipfile.
- Added setuptools==80.9.0 to project dependencies for pkg_resources API support.
- Pinned isort~=5.13.2 and pylint~=2.17.7 in dev dependencies to support line length options.
- Shared pinned Google client dependencies in vendor-appengine to avoid version mismatches with vendor-core.
- Deleted legacy Pipfile* and requirements.txt files.

Butler & Runtime Improvements:
- Updated _install_third_party in src/local/butler/common.py to use uv
  and avoid materializing requirements.txt into the repo, which are
  duplicative of `uv.lock` contents anyway.
- Updated src/local/butler/py_unittest.py to preserve src/third_party in
  sys.path while adding src/appengine/third_party, ensuring both App Engine
  and core libraries are importable during App Engine unit tests. This
  fixes an issue where the previous setup assumed third-party packages
  were not only vendored into third_party directories, but also
  installed into the virtual environment.
- Updated virtual environment guards in src/local/butler/guard.py and CASP
  CLI wrapper in cli/casp/src/casp/utils/container.py.

Scripts, CI, Docker & Docs:
- Updated setup scripts in local/ to install pipx and uv and activate .venv.
- Updated CI workflows to do the same.
- Updated Docker build scripts and Dockerfiles similarly.
- Updated bot setup scripts (configs/test/bot/setup/*.bash, windows-init.ps1).
- Updated developer and agent documentation.
- Updated local/tests/install_deps_test_linux.bash to verify fresh checkout
  environment setup with uv (.venv, third_party vendoring, bower components,
  and uv run butler.py lint / py_unittest).
TAG=agy
CONV=e3e8364e-3aff-4a79-9c78-17378c583b06
- pyproject.toml: Removed duplicate root dependencies already in vendor-core (cryptography, future, google-cloud-ndb, protobuf, psutil, crcmod); added conditional pywinauto dependency for win32 under dev group.
- windows-init.ps1: Reverted redundant PATH modification (pipx ensurepath handles PATH); updated uv sync to use --frozen.
- Dockerfiles: Removed redundant --upgrade and --no-deps flags from uv pip install -e /data.
- local/install_deps_linux.bash: Made optional gcloud apt packages conditional on rodete and added b/414408644 and b/484368884 references.
- local/install_deps_macos.bash: Removed redundant pipx check (handled by Brewfile); added --frozen to uv sync.
- local/install_python_deps_linux.bash: Removed extra source .venv/bin/activate call after nodeenv.
- src/local/butler/py_unittest.py: Removed unnecessary site.addsitedir call.
- CI/Bot setup scripts: Added --frozen flag to all automated uv sync invocations.

TAG=agy
CONV=e3e8364e-3aff-4a79-9c78-17378c583b06
- pyproject.toml: Moved setuptools==80.9.0 to [dependency-groups] dev.
- local/tests/ci_tests.bash & setup scripts: Reverted --frozen flag from uv sync.
- local/install_deps_linux.bash: Removed || true from apt-get install on rodete.
- Dockerfile: Added explanatory comment for uv and python3.11 symlinks.

TAG=agy
CONV=e3e8364e-3aff-4a79-9c78-17378c583b06
- pyproject.toml: Moved vendor-core, vendor-appengine, and vendor-platform-specific to [project.optional-dependencies] extras: core, appengine, platform-specific. Added comment explaining special handling for platform-specific binary wheel downloads via pip download in common.py _install_platform_pip.
- src/local/butler/common.py: Updated _install_third_party and _install_platform_pip to use uv export --extra.

TAG=agy
CONV=e3e8364e-3aff-4a79-9c78-17378c583b06
- local/README.md, AGENTS.md, kubernetes_e2e_test.bash, devcontainer.json: Removed redundant explicit pipx/uv installation steps as local/install_deps.bash handles uv setup.
- pyproject.toml: Removed redundant packages from appengine optional dependencies group that are already included in core. Updated comment on platform-specific extras to clarify vendoring behavior.
- uv.lock: Updated lockfile.

TAG=agy
CONV=1906a716-4a06-47e6-b2fb-fc2038c78ef6
…ests.bash

TAG=agy
CONV=1906a716-4a06-47e6-b2fb-fc2038c78ef6
- Configured [[tool.uv.index]] with PyPI (https://pypi.org/simple) as the default index in pyproject.toml.
- Regenerated uv.lock so all package sources and wheel URLs point to PyPI/files.pythonhosted.org rather than Google internal pkg.dev.

TAG=agy
CONV=1906a716-4a06-47e6-b2fb-fc2038c78ef6
The pre-built ci docker image does not have uv installed. Install
pipx and use it to install uv before executing the CI test pipeline.

TAG=agy
CONV=1906a716-4a06-47e6-b2fb-fc2038c78ef6
Avoid Cloud Build substitution parsing errors on shell environment variables
by extracting the CI setup and test invocation into a dedicated script.

TAG=agy
CONV=1906a716-4a06-47e6-b2fb-fc2038c78ef6
Parameterize local/tests/ci_tests_inner.bash to accept the test runner
command (defaulting to local/tests/run_tests) and reuse it in cloudbuild.yaml.

TAG=agy
CONV=1906a716-4a06-47e6-b2fb-fc2038c78ef6
TAG=agy
CONV=1906a716-4a06-47e6-b2fb-fc2038c78ef6
TAG=agy
CONV=1906a716-4a06-47e6-b2fb-fc2038c78ef6
@letitz
letitz force-pushed the typing-mechanical branch from 6571867 to 402421e Compare August 14, 2026 13:49
@letitz
letitz force-pushed the typing-mechanical branch from 402421e to 59457d7 Compare August 14, 2026 18:41
This commit introduces Python type annotations across the codebase with
zero runtime behavioral impact.

TAG=agy
CONV=1906a716-4a06-47e6-b2fb-fc2038c78ef6
@letitz
letitz force-pushed the typing-mechanical branch from 59457d7 to 21e6419 Compare August 14, 2026 19:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant