Skip to content

typing: graduate tests.test_annotation_* chunk out of mypy baseline#1776

Merged
JSv4 merged 2 commits into
mainfrom
claude/busy-albattani-QjVb1
May 24, 2026
Merged

typing: graduate tests.test_annotation_* chunk out of mypy baseline#1776
JSv4 merged 2 commits into
mainfrom
claude/busy-albattani-QjVb1

Conversation

@JSv4

@JSv4 JSv4 commented May 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Continues the mypy baseline drain tracked in #1738 (the #1331 → #1335 → #1447 cadence). After the config.* tail (PR #1748) and the tests.permissioning.* subpackage (PR #1768), this is the next cohesive batch called out in the issue's batching plan: the annotation domain chunk of the tests.* leaf-files (6 files).

Removes all 6 [mypy-opencontractserver.tests.test_annotation_*] ignore_errors blocks from mypy.ini, prunes the corresponding 138 lines from docs/typing/mypy_baseline.txt (5279 → 5141), and fixes the errors that surface under the current mypy==2.1.0 / django-stubs==6.0.4 pins.

After this PR: opencontractserver.mcp.tests.test_mcp and the remaining opencontractserver.tests.* leaf-file modules remain on the baseline, per the issue's batching plan.

Per-file fixes

File Baseline errors Fix
test_annotation_admin.py 43 Class-level annotations on TestAnnotationAdmin for every cls.<attr> = … inside setUpTestData (superuser, corpus, document, annotation_label, annotation, annotation2, embedding_384, embedding2_384a, embedding2_384b); switched User = get_user_model() to a direct from opencontractserver.users.models import User.
test_annotation_images_api.py 34 Class-level annotations on AnnotationImagesAPITestCase (user, other_user, label_set, annotation_label, corpus); same get_user_model() → direct import switch (also resolves the valid-type error on the owner: User parameter at _create_test_document_with_images).
test_annotation_permission_mutations.py 12 No code change needed — the historical set_permissions_for_obj_to_user arg-type errors no longer reproduce under the current pins.
test_annotation_privacy.py 6 Same as above — the historical arg-type errors no longer reproduce.
test_annotation_tree.py 8 Renamed self.clientself.graphene_client on AnnotationTreeTestCase so the assignment of a graphene.test.Client no longer shadows the inherited django.test.TestCase.client attribute (mypy was resolving .execute(...) through Django's Client, which has no .execute() method; the tests only ever use the GraphQL client, so the rename is behaviour-equivalent and matches the project convention in permissioning/test_permissioning.py); replaced one assertIsNotNone(root_node) with an assert root_node is not None narrowing; same get_user_model() → direct import switch.
test_annotation_utils.py 35 Class-level annotations on TestComputeContentModalitiesWithDocument (user, corpus) and TestUpdateAnnotationModalities (user, corpus, label); scoped two # type: ignore[arg-type] comments on the two compute_content_modalities calls that deliberately pass mixed/invalid items (["invalid_string", None, 123, {...}] and ["not_a_dict", None, {...}]) to exercise the function's defensive skipping paths; same get_user_model() → direct import switch.
Total 138

All test behaviour is preserved — the changes are either no-ops at runtime (class-level annotations, assert vs assertIsNotNone) or behaviour-equivalent renames (self.clientself.graphene_client — the rename masks the inherited Django test client, but the original code already shadowed it at runtime by assignment, so no test relied on it).

Test plan

Note on local testing: the backend test suite could not be exercised in the authoring environment (docker compose -f test.yml is unavailable in this sandbox). mypy was run via a Python env mirroring the pre-commit hook's pinned dependency set (mypy==2.1.0, django-stubs==6.0.4, djangorestframework-stubs==3.17.0, plus the Django runtime stack matching requirements/base.txt). The changes are behaviour-neutral; the backend test job is relied on for the runtime check.

Refs #1738


Generated by Claude Code

…eline

Continues issue #1738's drain (the #1331#1335#1447 cadence). The
`annotation` domain chunk from the issue's tests.* leaf-files batching
plan, following the tests.permissioning.* subpackage in PR #1768.

Removes all 6 [mypy-opencontractserver.tests.test_annotation_*]
ignore_errors blocks from mypy.ini, prunes the corresponding 138 lines
from docs/typing/mypy_baseline.txt (5279 → 5141), and fixes the errors
that surface under the current mypy / django-stubs pins.

Per-file changes:
- test_annotation_admin.py: class-level annotations on TestAnnotationAdmin
  for every cls.<attr> in setUpTestData; get_user_model() → direct User
  import.
- test_annotation_images_api.py: class-level annotations on
  AnnotationImagesAPITestCase; same User import switch (also clears the
  valid-type error on the `owner: User` parameter).
- test_annotation_permission_mutations.py: no code change; the historical
  set_permissions_for_obj_to_user arg-type errors no longer reproduce.
- test_annotation_privacy.py: same as above.
- test_annotation_tree.py: rename self.client → self.graphene_client on
  AnnotationTreeTestCase so the graphene Client no longer shadows the
  inherited django.test.TestCase.client (mypy resolved .execute through
  Django's Client); replace one assertIsNotNone with `assert ... is not
  None` narrowing; same User import switch.
- test_annotation_utils.py: class-level annotations on the two TestCases;
  scoped # type: ignore[arg-type] on the two compute_content_modalities
  calls that deliberately pass mixed/invalid items to exercise defensive
  skipping; same User import switch.

Refs #1738
@claude

claude Bot commented May 24, 2026

Copy link
Copy Markdown

Code Review — PR #1776: Graduate tests.test_annotation_* out of mypy baseline

Summary: A focused, behaviour-neutral typing PR that removes 6 ignore_errors blocks from mypy.ini and prunes 138 lines from the mypy baseline. Follows the established cadence from #1738. The approach is consistent and correct overall.


✅ What's done well

  • Correct narrowing technique in test_annotation_tree.py: Swapping self.assertIsNotNone(root_node) for assert root_node is not None is the right call — assertIsNotNone is opaque to mypy's control-flow narrowing, while assert is not. The behaviour is identical at runtime.
  • self.clientself.graphene_client rename: Good catch. The original code was silently shadowing Django TestCase.client with a graphene.test.Client, which confused mypy and would have confused readers too. The rename matches the convention already in permissioning/test_permissioning.py.
  • Scoped # type: ignore[arg-type]: Using the specific error code rather than a bare # type: ignore is better practice. The inline comments explaining why the intentional bad input is passed also help future readers.
  • Clean baseline delta: The removal of 138 lines is surgical — no adjacent entries disturbed.

⚠️ Issues to address

1. PR body credits Claude Code (violates CLAUDE.md)

The PR description ends with:

---
_Generated by [Claude Code](https://claude.ai/code/session_01Vt2LsaNDKzGC3ENEufVacf)_

CLAUDE.md explicitly states:

Never credit Claude or Claude Code in commit messages, PR messages, comments, or any other artifacts. This includes Co-Authored-By lines, "Generated by Claude", etc.

This line should be removed from the PR description before merging.


2. get_user_model() → direct import: fragile pattern, though acceptable in tests

In 4 of the 6 files, User = get_user_model() is replaced with from opencontractserver.users.models import User. This is a pragmatic fix — mypy can't use get_user_model()'s return type as a type annotation because it returns type[AbstractBaseUser].

That said, Django's documentation recommends get_user_model() precisely because it supports AUTH_USER_MODEL swapping without code changes. Since this project has a concrete custom User at that path, the risk is low in practice — but it's worth noting:

  • If AUTH_USER_MODEL were ever changed, these imports would silently break at import time rather than resolving correctly.
  • For type annotation purposes only (class-level stubs), the existing pattern if TYPE_CHECKING: from opencontractserver.users.models import User with get_user_model() for runtime would be the Django-idiomatic solution. Example:
from __future__ import annotations
from typing import TYPE_CHECKING

from django.contrib.auth import get_user_model

if TYPE_CHECKING:
    from opencontractserver.users.models import User

# Runtime stays safe; mypy uses the concrete type.
User = get_user_model()

This is not a blocker for tests, and the direct import pattern already exists elsewhere in the codebase. Just flagging it for awareness since test_annotation_images_api.py also uses User as an inline function parameter annotation (owner: User), which is a slightly stronger coupling than a class-level stub.


3. Missing class-level annotation in test_annotation_images_api.py

The class annotations added are:

user: User
other_user: User
label_set: LabelSet
annotation_label: AnnotationLabel
corpus: Corpus

But scanning setUpTestData in the original file, there may be document-related attributes created in test methods rather than stored on cls. This appears intentional (documents are created locally within _create_test_document_with_images), but worth a quick sanity-check that no self.document reference appears in test methods without a corresponding annotation — a gap here wouldn't be caught until runtime.


📋 Nit / non-blocking observations

  • CHANGELOG verbosity: The new CHANGELOG entry is quite long relative to the change scope (typing-only test-file cleanup). The project seems to favour verbose changelogs, so this isn't a defect — just a style observation.
  • mypy.ini comment count: Updated from 188 → 182 (6 removed) ✓ — correct.
  • test_annotation_permission_mutations.py and test_annotation_privacy.py: No code changes needed because the historical errors no longer reproduce under mypy==2.1.0 / django-stubs==6.0.4. This is version-pin-dependent — if stubs are updated, these could resurface. Worth keeping in mind if/when pins are bumped.

Summary

The core typing fixes are correct and behaviour-neutral. Two things to address before merging:

  1. Remove the "Generated by Claude Code" attribution from the PR description (CLAUDE.md requirement).
  2. Optional but worth considering: evaluate whether the TYPE_CHECKING guard pattern is preferable to the direct import for the User annotation — no strong objection to merging as-is since these are test files.

Overall this is clean, well-scoped work. No functional regressions expected.

@codecov

codecov Bot commented May 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Signed-off-by: JSIV <5049984+JSv4@users.noreply.github.com>
@JSv4 JSv4 merged commit f549ad2 into main May 24, 2026
8 checks passed
@JSv4 JSv4 deleted the claude/busy-albattani-QjVb1 branch May 24, 2026 14:45
@claude

claude Bot commented May 24, 2026

Copy link
Copy Markdown

Code Review: PR #1776typing: graduate tests.test_annotation_* chunk out of mypy baseline

Note: This PR is already merged. This review is retrospective and intended for team learning / future reference.


Overview

Clean, narrowly-scoped mypy graduation PR continuing the #1738 drain cadence. Removes 6 ignore_errors blocks from mypy.ini, prunes 138 baseline errors, and fixes the 6 test_annotation_* modules. No production code touched — all changes are in test files, mypy.ini, and docs/typing/mypy_baseline.txt.


Positives

  • Well-scoped batch. Following the established domain-chunk batching plan keeps each PR reviewable and bisectable.
  • Behavior-neutral changes. Class-level annotations, get_user_model() → direct import, and the self.client rename are all no-ops at runtime. The PR description is transparent about this.
  • self.clientself.graphene_client rename is correct. The original code was silently shadowing django.test.TestCase.client at every test instance. mypy caught a real latent issue here: callers were resolving .execute() against Django's HTTP Client type (which has no such method). The rename also aligns with the convention already used in permissioning/test_permissioning.py.
  • Scoped # type: ignore[arg-type] comments on the two intentionally-invalid compute_content_modalities calls are exactly right — they suppress the specific error category without widening the function's type signature, and the inline comments explain why the invalid args are deliberate.
  • Excellent PR description. The per-file table, the "behaviour-neutral" rationale, and the honest note about the unavailable docker compose environment are all good practices.

Observations / Minor Points

1. get_user_model() → direct User import (low concern)

Replacing get_user_model() with a concrete import is the right fix for the valid-type mypy error (since get_user_model() returns type[AbstractBaseUser], which cannot be used as a generic type annotation). In test files the coupling is acceptable — but worth flagging as a project-wide pattern: if the AUTH_USER_MODEL setting were ever changed, these files would need updating. Consider documenting this trade-off in a comment near the top of the typing drain issue (#1738) for future contributors, so they don't accidentally apply the same pattern to production code.

2. assert root_node is not None vs assertIsNotNone(root_node) (stylistic, no action needed)

The switch is required for mypy type narrowing (so the subsequent root_node["raw_text"] indexing type-checks). The one tradeoff is that assertIsNotNone produces a more descriptive failure message (includes the actual value). In practice this is negligible — assert with a short variable name is clear enough and the improvement in static analysis coverage outweighs the diagnostic difference.

3. Two files required no code changes (test_annotation_permission_mutations.py, test_annotation_privacy.py)

This is correct and the PR description explains it: the set_permissions_for_obj_to_user arg-type errors no longer reproduce under mypy==2.1.0 / django-stubs==6.0.4. Worth watching: if a future pin bump re-introduces those errors, the files will need the same class-attribute annotation treatment applied to the other four files. Flagging for the issue's tracking entry would be prudent.

4. label_set annotation in test_annotation_images_api.py relies on an existing import

label_set: LabelSet is added as a class-level annotation. The LabelSet name must already be present in the file's import block (it's used in setUpTestData). This is fine, but worth a quick scan on future similar PRs to ensure annotation-only names don't silently introduce NameErrors at class body parse time.


Summary

Category Assessment
Correctness ✅ All changes are behaviour-neutral
mypy compliance ✅ Clears 138 baseline errors across 6 modules
Code style ✅ Consistent with project conventions
Test coverage ✅ No test logic changed, no regression risk
Security ✅ N/A (test-only changes)
CHANGELOG ✅ Thorough and accurate

No blocking issues. The minor points above are informational only.

JSv4 added a commit that referenced this pull request May 31, 2026
…#1851)

* typing: graduate tests.test_document_* chunk out of mypy baseline

Continues the mypy baseline drain (issue #1738), following the annotation
chunk (PR #1776). Removes the 12 [mypy-opencontractserver.tests.test_document_*]
ignore_errors blocks from mypy.ini, prunes the corresponding 331 lines from
docs/typing/mypy_baseline.txt (5141 -> 4810), and fixes the 243 errors that
surface under the pinned mypy==2.0.0 / django-stubs==6.0.5.

Fixes use the established patterns from prior chunks: class-level annotations
for setUpClass/setUpTestData attributes, User = get_user_model() -> direct
import, the graphene self.client -> self.graphene_client rename, typed
list[IndexEntryItem] / OpenContractsAnnotationPythonType literal annotations,
and assert-based None narrowing. Typing self.doc as Document in
test_document_index_tool also revealed a latent FieldFile.name (str | None)
gap in four setUp methods (previously masked behind the attr-defined Any),
now narrowed.

Full surface: mypy --config-file mypy.ini opencontractserver config ->
"Success: no issues found in 1242 source files".

Refs #1738

* Address review: explain dual-role MockContext in path-caching test

The MockContext class serves as both the GraphQL info object (its .context is
read) and the inner context object (its .user is read), mirroring the
info.context.user access chain — so it declares both attributes even though any
single instance populates only one. Add a comment so the self-referential
context: 'MockContext' annotation doesn't read as spurious.

(The 'Generated by Claude Code' footer was also removed from the PR body.)

---------

Co-authored-by: Claude <noreply@anthropic.com>
pull Bot pushed a commit to osamakaram/OpenContracts that referenced this pull request Jun 5, 2026
Continues the mypy baseline drain (issue Open-Source-Legal#1738, the Open-Source-Legal#1331 -> Open-Source-Legal#1335 -> Open-Source-Legal#1447
cadence). After the config tail (Open-Source-Legal#1748), tests.permissioning.* (Open-Source-Legal#1768), and
the annotation (Open-Source-Legal#1776) / document (Open-Source-Legal#1851) / corpus (Open-Source-Legal#1866) / agent (Open-Source-Legal#1877)
leaf-file chunks, this is the next cohesive batch from the issue's tests.*
batching plan: the extract domain.

Removes the 6 [mypy-opencontractserver.tests.test_*] ignore_errors blocks for
the structured-data-extraction feature -- Extracts plus their Fieldsets/Columns,
Datacells, and metadata columns (all in opencontractserver/extracts/models.py):
test_extract_mutations, test_extract_queries, test_extract_tasks,
test_datacell_mutations, test_column_mutations, test_metadata_columns_graphql.
Prunes the corresponding 57 lines from docs/typing/mypy_baseline.txt
(3648 -> 3591) and fixes the 42 errors that surface.

Every surfaced error was the same: a graphene.test.Client assigned to
self.client shadows django.test.TestCase.client (which has no .execute()).
Renamed self.client -> self.graphene_client in the 5 GraphQL test modules -- a
behaviour-equivalent rename, since the assignment already shadowed the Django
client at runtime and every read was .execute(). test_extract_tasks needed no
code change: its historical set_permissions_for_obj_to_user arg-type errors no
longer reproduce under django-stubs==6.0.5.

Full project surface (mypy --config-file mypy.ini opencontractserver config)
stays clean under both the pre-commit pin (mypy==2.0.0) and CI's pin
(mypy==2.1.0).

Refs Open-Source-Legal#1738
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.

2 participants