Skip to content

feat: Migrate to google-genai SDK, fix vulnerability, and add examples - #4

Merged
rishitank merged 5 commits into
mainfrom
feature/upgrade-deps-and-examples
Feb 5, 2026
Merged

rishitank merged 5 commits into
mainfrom
feature/upgrade-deps-and-examples

Conversation

@rishitank

Copy link
Copy Markdown
Owner

Summary

This PR completes three tasks:

1. ✅ Fix Dependabot Vulnerability (CVE-2026-0994)

The old google-generativeai package required protobuf<6.0.0.dev0, which had a high severity vulnerability. By migrating to the new google-genai SDK, this constraint is removed and protobuf is now at 6.33.5 (no vulnerabilities).

2. ✅ Migrate to google-genai SDK

The old google-generativeai package was deprecated. This PR migrates to the new google-genai SDK with these changes:

  • Changed from genai.configure() + genai.GenerativeModel() to genai.Client(api_key=...)
  • Changed from sync methods to async methods via client.aio.files.upload(), client.aio.models.generate_content(), etc.
  • Used types.Part.from_uri(), types.Part.from_bytes(), types.Part.from_text() for content parts
  • Updated all tests to work with the new SDK mocking patterns

3. ✅ Add Examples

Added an examples/ directory with 3 demo scripts:

Script Description
basic_animation_check.py Record and analyze animations on a webpage
screenshot_analysis.py Static page visual analysis for layout/design issues
accessibility_check.py Visual accessibility checks (contrast, readability)

Testing

  • ✅ All 49 tests pass
  • ✅ Ruff lint passes
  • ✅ Mypy type check passes
  • ✅ pip-audit shows no vulnerabilities

Changes

  • pyproject.toml: Replaced google-generativeai with google-genai>=1.0.0
  • src/animawatch/vision.py: Migrated to new SDK API
  • tests/test_vision.py: Updated mocks for new SDK
  • tests/test_server.py: Added type ignores for mock assertions
  • examples/: New directory with 3 example scripts + README

Pull Request opened by Augment Code with guidance from the PR author

- Replace deprecated google-generativeai with google-genai SDK
- Resolves protobuf vulnerability CVE-2026-0994 (was pinned <6.0 by old SDK)
- Update vision.py to use new genai.Client() async API
- Update test_vision.py for new SDK mocking patterns
- Add type ignore comments for mypy false positives on mock assertions
- Add examples directory with 3 demo scripts:
  - basic_animation_check.py: Record and analyze animations
  - screenshot_analysis.py: Static page visual analysis
  - accessibility_check.py: Visual accessibility checks
@rishitank
rishitank enabled auto-merge (squash) February 5, 2026 18:12
@coderabbitai

coderabbitai Bot commented Feb 5, 2026 •

Copy link
Copy Markdown

Warning

Rate limit exceeded

@rishitank has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 19 minutes and 20 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

Apprentice: Adds example scripts and a README demonstrating AnimaWatch workflows, migrates GeminiProvider internals from the global google.generativeai API to the client-based google.genai async API, updates the dependency, and adjusts tests to mock and assert the new client-based interactions.

Changes

Cohort / File(s) Summary
Example Demonstrations
examples/README.md, examples/basic_animation_check.py, examples/screenshot_analysis.py, examples/accessibility_check.py
Adds documentation and three runnable example scripts plus README: basic animation recording/analysis, screenshot analysis, and accessibility analysis. Each example manages browser lifecycle, captures media, runs vision analysis, and cleans up artifacts.
Gemini API Migration
src/animawatch/vision.py, pyproject.toml
Refactors GeminiProvider to use google.genai client-based async API (GenAI.Client with aio.files/aio.models) and updates dependency from google-generativeai to google-genai. Media upload/get/delete and generate_content flows moved to Part-based payloads and async client methods; adds MAX_PROCESSING_SECONDS.
Test Suite Updates
tests/test_vision.py, tests/test_server.py
Adapts tests to the client-based API: mocks for genai.Client and its aio.files/aio.models methods and updates assertions/casts to match AsyncMock interfaces. Minor typing/assertion adjustments in server tests.
Manifest / Project
pyproject.toml
Replaces google-generativeai dependency with google-genai.

Sequence Diagram(s)

sequenceDiagram
    participant Recorder as BrowserRecorder
    participant FS as Local File System
    participant GenAI as genai.Client (aio)
    participant Model as Gemini Model (generate_content)

    Recorder->>FS: write video/image bytes
    FS-->>Recorder: file path / bytes
    Recorder->>GenAI: aio.files.upload(file bytes)
    GenAI-->>Recorder: file_id / uri (processing)
    loop poll until ACTIVE or timeout (MAX_PROCESSING_SECONDS)
        Recorder->>GenAI: aio.files.get(file_id)
        GenAI-->>Recorder: file state
    end
    Recorder->>GenAI: aio.models.generate_content(model, contents: [Part(file), Part(prompt)])
    GenAI-->>Model: invoke model
    Model-->>GenAI: analysis response (text)
    GenAI->>GenAI: aio.files.delete(file_id)
    GenAI-->>Recorder: return analysis text
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

Apprentice, the changes rise like night,
Examples march, and genai wields its might,
Async whispers, files ascend and fall,
Tests obey — the Dark Side watches all,
Cleanups close the loop; the integration stands tall.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarises the three primary objectives: migration to google-genai SDK, vulnerability fix, and addition of examples. It is concise, specific, and directly related to the main changeset.
Description check ✅ Passed The description is well-structured and directly related to all aspects of the changeset. It clearly explains the three main tasks (vulnerability fix, SDK migration, and examples), provides testing evidence, and lists file changes—all aligned with the actual modifications.
Docstring Coverage ✅ Passed Docstring coverage is 95.45% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/upgrade-deps-and-examples

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/test_vision.py (2)

21-33: ⚠️ Potential issue | 🟡 Minor

Avoid asserting internal state, Apprentice.

provider.model_name is an internal detail; favour behaviour-based assertions to reduce coupling.

💡 Proposed change
-            assert provider.model_name == "gemini-2.0-flash"

132-158: ⚠️ Potential issue | 🟡 Minor

Add an empty/exception response case for image analysis, Apprentice.

Only the happy path is covered; add a test for a blank response (or raised error) to satisfy edge-case coverage.

🧪 Example edge-case test
@@
     async def test_analyze_image_reads_and_processes(self, tmp_path: Path) -> None:
         """Test that analyze_image reads image and calls generate_content."""
@@
         assert result == "Image analysis"
         mock_client.aio.models.generate_content.assert_called_once()
+
+    `@pytest.mark.asyncio`
+    async def test_analyze_image_returns_empty_on_blank_response(self, tmp_path: Path) -> None:
+        """Test that analyze_image returns an empty string when response has no text."""
+        image_path = tmp_path / "test.png"
+        image_path.write_bytes(b"fake image data")
+
+        with (
+            patch("animawatch.vision.settings") as mock_settings,
+            patch("animawatch.vision.genai") as mock_genai,
+        ):
+            mock_settings.gemini_api_key = "test-api-key"
+            mock_settings.vision_model = "gemini-2.0-flash"
+
+            mock_response = MagicMock()
+            mock_response.text = None
+            mock_client = MagicMock()
+            mock_client.aio.models.generate_content = AsyncMock(return_value=mock_response)
+            mock_genai.Client.return_value = mock_client
+
+            provider = GeminiProvider()
+            result = await provider.analyze_image(image_path, "Analyze image")
+
+            assert result == ""
🤖 Fix all issues with AI agents
In `@examples/accessibility_check.py`:
- Around line 1-7: Run the project's formatter (uv run ruff format) on the
example file to fix the formatting CI failure: reformat the shebang line and
module-level docstring so they comply with Ruff's rules, save and commit the
updated file (you can run uv run ruff format <filename> locally or via the repo
script to apply the fix).
- Around line 16-78: The temporary screenshot is only removed before the return,
so if vision.analyze_image raises the file remains; update check_accessibility
to ensure screenshot_path is deleted in the finally block (after await
browser.stop() or before stopping) by checking screenshot_path exists (and is
not None) and unlinking it; keep BrowserRecorder usage (browser.start,
browser.stop) and ensure screenshot_path is defined in the outer scope of the
try so the finally can access it and safely handle failures from analyze_image.

In `@examples/basic_animation_check.py`:
- Around line 1-7: Run the code formatter (ruff) on the module
basic_animation_check.py and commit the changes: execute "uv run ruff format"
(or "ruff format") to reformat the shebang, docstring and surrounding whitespace
so the file matches the project's style rules, then stage and push the formatted
file.
- Around line 16-65: The temporary recording may be left behind if
vision.analyze_video raises; in check_animations initialize video_path = None
before recording, then move the removal logic into the finally block so the file
is always considered for deletion (only when output_dir is None and video_path
is not None and video_path.exists()), keeping the existing await browser.stop()
call in finally; this ensures cleanup happens on both success and failure while
avoiding NameError if record_interaction fails.

In `@examples/README.md`:
- Around line 7-95: Fix the Markdown fencing and trailing whitespace: ensure
there is a blank line before and after each triple-backtick fenced block (e.g.,
the initial bash block containing "cd ~/github/animawatch", the GEMINI_API_KEY
block with export GEMINI_API_KEY, and the Ollama example block), remove the
stray leading/trailing spaces inside those fences, and delete the trailing space
at the end of the Note line that mentions basic_animation_check.py so the line
reads "**Note**: Ollama doesn't support direct video analysis, so
`basic_animation_check.py`" with no trailing whitespace.

In `@examples/screenshot_analysis.py`:
- Around line 1-7: Run Ruff's auto-formatter on the affected file
(examples/screenshot_analysis.py) to fix the CI formatting failure: execute the
formatter via your project scripts (e.g., "uv run ruff format") or directly with
ruff, review the changed docstring/shebang formatting, stage the updated file,
and commit the change so the CI formatting check passes.
- Around line 15-64: The temp screenshot is only deleted after a successful
analysis; if vision.analyze_image raises the file remains. Initialize
screenshot_path = None before the try in analyze_screenshot, then move the file
cleanup into the existing finally block so it always runs: in finally, if
screenshot_path is not None and screenshot_path.exists() then unlink it, and
still await browser.stop() (preserve BrowserRecorder.start/stop usage). This
ensures cleanup runs on failure while keeping the browser shutdown logic in
analyze_screenshot.

In `@src/animawatch/vision.py`:
- Around line 10-11: Wrap the long-running await in the polling loop with an
asyncio.timeout (e.g., asyncio.timeout(300)) so the loop aborts after a bounded
time instead of awaiting forever, and replace the overly broad
contextlib.suppress(Exception) with a targeted suppression such as
contextlib.suppress(FileNotFoundError, types.NotFound) so asyncio.CancelledError
is not swallowed; specifically update the polling loop await to use
asyncio.timeout(300) and change calls referencing contextlib.suppress(Exception)
to suppress only FileNotFoundError and types.NotFound.
- Around line 43-50: The polling loop that waits for video processing (the while
using self.client.aio.files.get after upload) lacks a timeout and can hang
indefinitely; add a maximum wait boundary (e.g., MAX_PROCESSING_SECONDS) by
recording a start time or using asyncio.wait_for around the polling coroutine,
check elapsed time each iteration, and raise a clear TimeoutError including the
file_name and elapsed time when exceeded; update the upload-and-wait logic in
the same method (the video_file = await self.client.aio.files.upload(...) block
and the following while loop) to enforce this timeout and cleanly abort polling.

In `@tests/test_server.py`:
- Around line 218-219: Replace the "type: ignore[attr-defined]" comments by
casting the mocked attributes to AsyncMock so type-checkers recognize assertion
methods: import cast and AsyncMock, then use patterns like cast(AsyncMock,
mock_app_context.browser).record_interaction.assert_called_once() and
cast(AsyncMock, mock_app_context.vision).analyze_video.assert_called_once().
Apply the same cast to the other five assertion spots (the remaining calls on
mock_app_context.browser and mock_app_context.vision in the test file) so all
seven assertions use cast(AsyncMock, ...) instead of type ignores.

Comment thread examples/accessibility_check.py
Comment thread examples/accessibility_check.py
Comment thread examples/basic_animation_check.py
Comment thread examples/basic_animation_check.py
Comment thread examples/README.md
Comment thread examples/screenshot_analysis.py
Comment thread examples/screenshot_analysis.py
Comment thread src/animawatch/vision.py
Comment thread src/animawatch/vision.py
Comment thread tests/test_server.py Outdated
- Add timeout (MAX_PROCESSING_SECONDS=300) to video processing poll loop
- Move temp file cleanup to finally blocks in all example files
- Replace type: ignore comments with cast(AsyncMock, ...) pattern
- Initialize temp path variables before try blocks for proper cleanup
- Remove internal state assertion (provider.model_name) in test_vision.py
- Add edge case test for empty/blank response in image analysis
- Now 50 tests total

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/test_vision.py (1)

21-34: ⚠️ Potential issue | 🟡 Minor

Remove the constructor mock assertion, Apprentice. The inspect of genai.Client initialization is an internal detail; the true measure of your provider's power manifests when it summons the Dark Side through analyse_video and analyse_image methods. The provider's creation is sufficiently validated by its mere existence.

🔧 Minimal change
-            mock_genai.Client.assert_called_once_with(api_key="test-api-key")
-            # Verify provider was created (avoid asserting internal state)
-            assert provider is not None
+            assert provider is not None

As per the coding guidelines for tests/**/*.py: No implementation details tested (test behaviour, not internals).

src/animawatch/vision.py (1)

36-42: ⚠️ Potential issue | 🟡 Minor

Annotate these attributes with their types, Apprentice. The Dark Side demands clarity—these instance variables must bear witness to their types to satisfy the repository's type-safety doctrine.

Proposed change
-        self.client = genai.Client(api_key=settings.gemini_api_key)
-        self.model_name = settings.vision_model
+        self.client: genai.Client = genai.Client(api_key=settings.gemini_api_key)
+        self.model_name: str = settings.vision_model

As per the coding guidelines for src/**/*.py: Type hints everywhere (no Any unless justified).

@rishitank

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Feb 5, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

- Remove implementation detail assertion in test_init_with_api_key_creates_client
- Add type annotations to GeminiProvider.client and .model_name
@rishitank

Copy link
Copy Markdown
Owner Author

@coderabbitai approve

All the issues from the previous review have been addressed:

  • ✅ Formatting with ruff - fixed
  • ✅ Temp file cleanup moved to finally blocks - fixed
  • ✅ Polling timeout added (MAX_PROCESSING_SECONDS) - fixed
  • ✅ Type ignores replaced with cast(AsyncMock) - fixed
  • ✅ Internal state assertion removed - fixed
  • ✅ Edge case test for empty response added - fixed
  • ✅ Type annotations added to vision.py attributes - fixed

@rishitank
rishitank merged commit 7aeeb8b into main Feb 5, 2026
9 checks passed
@rishitank
rishitank deleted the feature/upgrade-deps-and-examples branch February 5, 2026 22:17
@coderabbitai

coderabbitai Bot commented Feb 5, 2026

Copy link
Copy Markdown

@rishitank, Excellent work, Apprentice. Your diligence in addressing every weakness I identified shows the growing power of the Dark Side within you.

The rebel scum of technical debt have been vanquished:

  • Formatting obeys the will of Ruff ✅
  • Resource cleanup is now... inevitable ✅
  • Timeouts prevent the processing from falling into an endless abyss ✅
  • Type safety grows stronger ✅
  • Tests are fortified ✅

Your migration to google-genai and the addition of examples demonstrate considerable power. The vulnerability CVE-2026-0994 has been crushed, and your codebase is most impressive.

I grant my approval. The Force is strong with this pull request.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

✅ Actions performed

Comments resolved and changes approved.

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