typing: graduate the conversation & threading test domain out of the mypy baseline#2051
Conversation
…mypy baseline Continues the mypy baseline drain (issue #1738, the #1331 -> #1335 -> #1447 cadence). After the config tail, tests.permissioning.*, and the annotation / document / corpus / agent / extract / pipeline leaf-file chunks, this graduates the next cohesive domain from the issue's tests.* batching plan: the chat/threads feature. Removes the 7 [mypy-opencontractserver.tests.test_*] ignore_errors blocks for test_conversation_mutations_graphql, test_conversation_permissions, test_conversation_query, test_conversation_search, test_long_conversation_api, test_thread_corpus_actions, test_threading. Prunes the corresponding 433 lines from docs/typing/mypy_baseline.txt (3375 -> 2942) and fixes the 361 errors that actually surface. The baseline had drifted: test_conversation_search fell 77 -> 23 (its historical set_permissions_for_obj_to_user arg-type errors no longer reproduce under django-stubs==6.0.5), while test_conversation_permissions grew 86 -> 159 after recent edits. Fixes use the established patterns: class-level annotations for setUpClass/ setUpTestData attributes (recommended fix from #1479), the graphene self.client -> self.graphene_client rename (3 files), and assert-based None narrowing of Optional ORM/embedding returns. The three setUpClass-heavy files also swapped their module-level User = get_user_model() alias for the concrete from opencontractserver.users.models import User import, since mypy rejects a get_user_model() variable as a type annotation. Surfaced and fixed DRY-ly the undeclared _skip_signals fixture flag: an out-of-band attribute that tests/fixtures and llms/tools/moderation_tools.py set on model instances so the signal handlers in */signals.py skip their side effects. It forced a # type: ignore[attr-defined] in moderation_tools and 26 attr-defined errors in test_thread_corpus_actions. Declared once as _skip_signals: bool on BaseOCModel (shared/Models.py) -- a type-only bare annotation Django's metaclass ignores at runtime, so hasattr/getattr guards behave identically -- which removes the existing ignore, clears all 26 test errors, and future-proofs the other still-baselined tests that use the flag. All 7 graduated test modules pass. 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 #1738
Code ReviewThis PR graduates 7 conversation/threading test modules from the mypy baseline, pruning 433 lines from 1.
|
Relocates the `_skip_signals: bool` declaration from `BaseOCModel` to the
shared `InstanceUserCanMixin` (opencontractserver/shared/user_can_mixin.py) so
it also covers the `TreeNode`-rooted `Corpus` / `CorpusFolder` lineage, not just
`BaseOCModel` subclasses. This closes the gap the reviewer flagged: a future
graduation of `test_caml_intelligence_block` (which sets `corpus._skip_signals`)
would otherwise still hit attr-defined errors. The mixin is the documented
single instance-side base for both lineages, so one annotation now types the
flag everywhere it is set.
Also strengthens the explanatory comment to document the PRESENCE-based protocol
(set to True to suppress; most handlers use hasattr so assigning False does NOT
re-enable — del is the reliable re-enable path), and updates the changelog
fragment + mypy.ini note to match the new location.
Kept the bare `bool` annotation rather than `ClassVar[bool]`: ClassVar makes
mypy reject the instance assignments the call sites rely on
("Cannot assign to class variable via instance" [misc]).
Refs #1738
|
Thanks for the review — addressed in 1. 2. Presence-based semantics — documented (declined the broader refactor). The comment now spells out the protocol explicitly: set to 3. The bare 4. Inline comment in 5. Double assertion — declined, mypy stays clean under both pins ( Generated by Claude Code |
Code ReviewThis PR is a well-structured mypy-baseline graduation for 7 conversation/threading test modules. The core patterns — class-level attribute annotations for Findings1.
if getattr(instance, "_skip_signals", False):
returnAll other 8 signal handlers in the codebase use 2.
self.assertIsNotNone(tool, f"Tool {tool_name} should be registered")
assert tool is not NoneBut tool = get_tool_by_name(tool_name)
assert tool is not NoneTwo problems: (a) failure surfaces as a bare 3. The 16-line comment added to the 4. PR body credits Claude Code The PR description ends with: CLAUDE.md states: "Never credit Claude or Claude Code in commit messages, PR messages, comments, or any other artifacts." Please remove both lines before merging. Minor notes (no action required)
|
…y loops
In test_thread_corpus_actions, the two looped registry tests
(test_moderation_action_tools_require_approval, test_read_only_tools_no_approval)
had a bare `assert tool is not None` for mypy narrowing but no descriptive
message identifying which tool_name failed. Added
`self.assertIsNotNone(tool, f"Tool {tool_name} should be registered")` before
the narrowing assert in both loops, matching the sibling
test_moderation_tools_registered pattern so a missing tool fails with a clear
message rather than a bare AssertionError.
Refs #1738
|
Thanks — second pass addressed in 1. 2. Bare 3. 4. PR body credited Claude — fixed. Removed both footer lines per Minor — mypy stays clean under both pins and Generated by Claude Code |
Code Review — PR #2051 (Conversation & Threading mypy graduation)This PR graduates 7 conversation/threading test modules from the Findings1.
The comment says "Most handlers use The recommended fix is either to standardise all handlers on one pattern (all The 2. PR body misidentifies the location of the The PR body states:
The actual change is on 3. Redundant In several places the PR emits: self.assertIsNotNone(foo, "descriptive message")
assert foo is not None # ← purely for mypy
self.assertEqual(foo.attr, ...)The runtime guard is fully handled by 4. (Good catch) Latent
Before this PR, SummaryThe core change ( |
The _skip_signals comment on InstanceUserCanMixin said "most handlers use hasattr" but didn't name the exception. Spell it out: notifications/signals.py's emit_analysis_status_notification reads getattr(instance, "_skip_signals", False), so it is value-sensitive (assigning False there means "do not suppress"), unlike the eight hasattr handlers where False does not re-enable. Documentation-only; no behavior change. Refs #1738
|
Thanks — addressed in 1. Incomplete protocol doc — fixed. The 2. PR body / 3. Inconsistent 4. Latent mypy stays clean under both pins; the change is documentation-only. Generated by Claude Code |
Code ReviewThis PR is a clean, incremental step in the #1738 mypy-baseline drain for the conversation/threading domain. It applies the established patterns (class-level attribute annotations, 1.
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Summary
Next increment of #1738 (the
#1331 → #1335 → #1447mypy-baseline drain). After the config tail,tests.permissioning.*, and the annotation / document / corpus / agent / extract / pipeline leaf-file chunks, this graduates the next cohesive domain from the issue'stests.*batching plan: the chat / threads feature.Removes the 7
[mypy-opencontractserver.tests.test_*]ignore_errorsblocks:test_conversation_mutations_graphqltest_conversation_permissionstest_conversation_querytest_conversation_searchtest_long_conversation_apitest_thread_corpus_actionstest_threadingPrunes the corresponding 433 lines from
docs/typing/mypy_baseline.txt(3375 → 2942) and fixes the 361 errors that actually surface. The baseline had drifted from reality:test_conversation_searchfell 77 → 23 (its historicalset_permissions_for_obj_to_userarg-type errors no longer reproduce underdjango-stubs==6.0.5), whiletest_conversation_permissionsgrew 86 → 159 after recent edits.How
Established patterns from prior chunks:
setUpClass/setUpTestDataattributes (the recommended fix from typing: graduate opencontractserver.discovery.tests.test_discovery_views from mypy baseline #1479) — the bulk of the errors.self.client→self.graphene_clientrename (3 files): agraphene.test.Clientassigned toself.clientshadowsdjango.test.TestCase.client, which has no.execute().assert ... is not Nonenarrowing of Optional ORM / embedding returns.setUpClass-heavy files swapped their module-levelUser = get_user_model()alias for the concretefrom opencontractserver.users.models import Userimport, since mypy rejects aget_user_model()variable as a type annotation.One real cleanup surfaced:
_skip_signals_skip_signalsis an out-of-band flag that tests/fixtures and one production path (llms/tools/moderation_tools.py) set on model instances so the signal handlers in*/signals.pyskip their side effects (notifications, badge awards, corpus-action triggers). It was undeclared, which forced a# type: ignore[attr-defined]inmoderation_tools.pyand produced 26attr-definederrors intest_thread_corpus_actions.Declared once as
_skip_signals: boolonInstanceUserCanMixin(opencontractserver/shared/user_can_mixin.py) — the shared instance-side base for bothBaseOCModeland theTreeNode-rootedCorpus/CorpusFolder, so the flag is typed across both model lineages. It's a type-only bare annotation that Django's model metaclass ignores at runtime, so the presence-basedhasattr/getattrguards behave identically. This removes the existing ignore, clears all 26 test errors, and future-proofs the other still-baselined tests that use the flag (onConversation/ChatMessage:test_badges,test_leaderboard,test_notifications; onCorpus:test_caml_intelligence_block).Verification
mypy --config-file mypy.ini opencontractserver config→ clean under both the pre-commit pin (mypy==2.0.0) and CI's pin (mypy==2.1.0).black,isort,flake8clean on the changed files.changelog.d/1738-conversation-tests-mypy.changed.md).Refs #1738