Skip to content

Commit c7a4040

Browse files
authored
Merge pull request #2051 from Open-Source-Legal/claude/zen-shannon-oxuzju
typing: graduate the conversation & threading test domain out of the mypy baseline
2 parents 8873b05 + bfea061 commit c7a4040

12 files changed

Lines changed: 174 additions & 494 deletions
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
- **Graduated the `conversation & threading` test domain out of the mypy
2+
baseline (#1738).** Removed the 7 `[mypy-opencontractserver.tests.test_*]`
3+
`ignore_errors` blocks for the chat/threads feature:
4+
`test_conversation_mutations_graphql`, `test_conversation_permissions`,
5+
`test_conversation_query`, `test_conversation_search`,
6+
`test_long_conversation_api`, `test_thread_corpus_actions`,
7+
`test_threading`. Pruned the corresponding 433 lines from
8+
`docs/typing/mypy_baseline.txt` (3375 → 2942) and fixed the 361 errors that
9+
actually surface. The baseline had drifted: e.g. `test_conversation_search`
10+
fell 77 → 23 (its historical `set_permissions_for_obj_to_user` arg-type
11+
errors no longer reproduce under `django-stubs==6.0.5`), while
12+
`test_conversation_permissions` grew 86 → 159 after recent edits. Fixes use
13+
the established patterns — class-level annotations for
14+
`setUpClass`/`setUpTestData` attributes (the recommended fix from #1479), the
15+
graphene `self.client``self.graphene_client` rename (3 files), and
16+
`assert ... is not None` narrowing of Optional ORM/embedding returns. The
17+
three `setUpClass`-heavy files also swapped their module-level
18+
`User = get_user_model()` alias for the concrete
19+
`from opencontractserver.users.models import User` import, since mypy rejects
20+
a `get_user_model()` variable as a type annotation.
21+
- **Declared the `_skip_signals` fixture flag on `InstanceUserCanMixin`
22+
(`opencontractserver/shared/user_can_mixin.py`).** `_skip_signals` is an
23+
out-of-band attribute that tests/fixtures and one production path
24+
(`llms/tools/moderation_tools.py`) set on model instances so the signal
25+
handlers in `*/signals.py` skip their side effects (notifications, badge
26+
awards, corpus-action triggers). It was undeclared, which forced a
27+
`# type: ignore[attr-defined]` in `moderation_tools.py` and produced 26
28+
`attr-defined` errors in `test_thread_corpus_actions`. Declaring it once as
29+
`_skip_signals: bool` on the mixin shared by `BaseOCModel` *and* the
30+
`TreeNode`-rooted `Corpus` / `CorpusFolder` (a type-only bare annotation;
31+
Django's model metaclass ignores it at runtime, so the presence-based
32+
`hasattr`/`getattr` guards still behave identically) removes the existing
33+
ignore, clears all 26 test errors, and types the flag across both model
34+
lineages — future-proofing the other still-baselined tests that use it on
35+
`Conversation`/`ChatMessage` (`test_badges`, `test_leaderboard`,
36+
`test_notifications`) as well as on `Corpus` (`test_caml_intelligence_block`).
37+
The full
38+
project surface (`mypy --config-file mypy.ini opencontractserver config`)
39+
stays clean under both the pre-commit pin (`mypy==2.0.0`) and CI's pin
40+
(`mypy==2.1.0`).

docs/typing/mypy_baseline.txt

Lines changed: 0 additions & 433 deletions
Large diffs are not rendered by default.

mypy.ini

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,22 @@ ignore_errors = True
136136
# but its body already handles MIME strings -- widened to
137137
# ``Optional[Union[str, FileTypeEnum]]`` (removing a now-unused
138138
# ``# type: ignore[arg-type]`` in ``tasks/doc_tasks.py``).
139+
#
140+
# Issue #1738 also graduated the 7-module ``conversation & threading`` domain
141+
# chunk (the chat/threads feature): ``test_conversation_mutations_graphql``,
142+
# ``test_conversation_permissions``, ``test_conversation_query``,
143+
# ``test_conversation_search``, ``test_long_conversation_api``,
144+
# ``test_thread_corpus_actions``, ``test_threading`` (433 lines pruned). Mostly
145+
# the ``setUpClass``/``setUpTestData`` class-attribute pattern plus the
146+
# ``self.client`` → ``self.graphene_client`` rename and ``assert ... is not
147+
# None`` narrowing; the three ``setUpClass``-heavy files also swapped their
148+
# ``User = get_user_model()`` alias for the concrete
149+
# ``from opencontractserver.users.models import User`` import (mypy rejects a
150+
# ``get_user_model()`` variable as a type annotation). Surfaced the
151+
# undeclared ``_skip_signals`` fixture flag — now declared once as
152+
# ``_skip_signals: bool`` on ``InstanceUserCanMixin`` (``shared/user_can_mixin.py``,
153+
# shared by ``BaseOCModel`` + ``Corpus``/``CorpusFolder``), removing a now-unused
154+
# ``# type: ignore[attr-defined]`` in ``llms/tools/moderation_tools.py``.
139155

140156
[mypy-opencontractserver.tests.base]
141157
ignore_errors = True
@@ -212,18 +228,6 @@ ignore_errors = True
212228
[mypy-opencontractserver.tests.test_compact_pawls]
213229
ignore_errors = True
214230

215-
[mypy-opencontractserver.tests.test_conversation_mutations_graphql]
216-
ignore_errors = True
217-
218-
[mypy-opencontractserver.tests.test_conversation_permissions]
219-
ignore_errors = True
220-
221-
[mypy-opencontractserver.tests.test_conversation_query]
222-
ignore_errors = True
223-
224-
[mypy-opencontractserver.tests.test_conversation_search]
225-
ignore_errors = True
226-
227231
[mypy-opencontractserver.tests.test_core_agents]
228232
ignore_errors = True
229233

@@ -314,9 +318,6 @@ ignore_errors = True
314318
[mypy-opencontractserver.tests.test_llm_tools]
315319
ignore_errors = True
316320

317-
[mypy-opencontractserver.tests.test_long_conversation_api]
318-
ignore_errors = True
319-
320321
[mypy-opencontractserver.tests.test_mcp_extended]
321322
ignore_errors = True
322323

@@ -479,12 +480,6 @@ ignore_errors = True
479480
[mypy-opencontractserver.tests.test_text_thumbnails]
480481
ignore_errors = True
481482

482-
[mypy-opencontractserver.tests.test_thread_corpus_actions]
483-
ignore_errors = True
484-
485-
[mypy-opencontractserver.tests.test_threading]
486-
ignore_errors = True
487-
488483
[mypy-opencontractserver.tests.test_thumbnails]
489484
ignore_errors = True
490485

opencontractserver/llms/tools/moderation_tools.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ def add_thread_message(
457457
creator_id=creator_id,
458458
agent_configuration=agent_config,
459459
)
460-
message._skip_signals = True # type: ignore[attr-defined]
460+
message._skip_signals = True
461461
message.save()
462462

463463
logger.info(

opencontractserver/shared/user_can_mixin.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,24 @@ class InstanceUserCanMixin:
153153
or fix the manager — never paper over the ``AttributeError``.
154154
"""
155155

156+
# Out-of-band signal-suppression flag (NOT a DB field). The signal
157+
# handlers in ``*/signals.py`` check it on the instance to skip their
158+
# side effects (notifications, badge awards, corpus-action triggers)
159+
# during fixture creation and a handful of internal saves (e.g.
160+
# ``llms/tools/moderation_tools.py``). Declared here — on the mixin shared
161+
# by ``BaseOCModel`` *and* the ``TreeNode``-rooted ``Corpus`` /
162+
# ``CorpusFolder`` — so the attribute is typed across both model lineages
163+
# (a bare annotation Django's metaclass ignores at runtime).
164+
#
165+
# The protocol is PRESENCE-based: set it to ``True`` to suppress. Most
166+
# handlers use ``hasattr(instance, "_skip_signals")``, so assigning
167+
# ``False`` does NOT re-enable signals — ``del instance._skip_signals`` is
168+
# the only reliable re-enable. (The lone exception is
169+
# ``notifications/signals.py``'s ``emit_analysis_status_notification``,
170+
# which reads ``getattr(instance, "_skip_signals", False)`` and so IS
171+
# value-sensitive.) Normally absent at runtime.
172+
_skip_signals: bool
173+
156174
def __getstate__(self) -> Any:
157175
"""Strip the Tier 1 permission cache before pickling.
158176

opencontractserver/tests/test_conversation_mutations_graphql.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def setUp(self):
6060
)
6161

6262
# Create GraphQL client
63-
self.client = Client(schema)
63+
self.graphene_client = Client(schema)
6464

6565
def _execute_with_user(self, query, user, variables=None):
6666
"""Execute a GraphQL query with a specific user context."""
@@ -72,7 +72,7 @@ def __init__(self, user):
7272
self.META = {}
7373

7474
context_value = MockRequest(user)
75-
return self.client.execute(
75+
return self.graphene_client.execute(
7676
query, variables=variables, context_value=context_value
7777
)
7878

@@ -122,7 +122,9 @@ def test_create_thread_mutation(self):
122122
# Verify initial message was created
123123
messages = ChatMessage.objects.filter(conversation=conversation)
124124
self.assertEqual(messages.count(), 1)
125-
self.assertEqual(messages.first().content, "This is the first message")
125+
first_message = messages.first()
126+
assert first_message is not None
127+
self.assertEqual(first_message.content, "This is the first message")
126128

127129
def test_create_thread_without_permission(self):
128130
"""Test creating a thread without corpus permission."""

opencontractserver/tests/test_conversation_permissions.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from types import SimpleNamespace
1818
from unittest.mock import patch
1919

20-
from django.contrib.auth import get_user_model
2120
from django.contrib.auth.models import AnonymousUser, Group
2221
from django.test import TestCase
2322
from guardian.shortcuts import assign_perm
@@ -32,15 +31,19 @@
3231
from opencontractserver.corpuses.models import Corpus
3332
from opencontractserver.documents.models import Document
3433
from opencontractserver.types.enums import PermissionTypes
35-
36-
User = get_user_model()
34+
from opencontractserver.users.models import User
3735

3836

3937
class TestConversationBifurcatedPermissions(TestCase):
4038
"""
4139
Test the bifurcated permission model for Conversation visibility.
4240
"""
4341

42+
superuser: User
43+
alice: User
44+
bob: User
45+
charlie: User
46+
4447
@classmethod
4548
def setUpClass(cls):
4649
super().setUpClass()
@@ -479,6 +482,10 @@ class TestChatMessageInheritedPermissions(TestCase):
479482
Test that ChatMessage visibility inherits from Conversation visibility.
480483
"""
481484

485+
alice: User
486+
bob: User
487+
charlie: User
488+
482489
@classmethod
483490
def setUpClass(cls):
484491
super().setUpClass()
@@ -691,6 +698,11 @@ class TestConversationGroupGrants(TestCase):
691698
extracts).
692699
"""
693700

701+
owner: User
702+
member: User
703+
outsider: User
704+
group: Group
705+
694706
@classmethod
695707
def setUpClass(cls):
696708
super().setUpClass()
@@ -898,6 +910,10 @@ class TestConversationService(TestCase):
898910
centralization roadmap.
899911
"""
900912

913+
alice: User
914+
bob: User
915+
superuser: User
916+
901917
@classmethod
902918
def setUpClass(cls):
903919
super().setUpClass()
@@ -1123,6 +1139,8 @@ class TestEdgeCases(TestCase):
11231139
Test edge cases and boundary conditions.
11241140
"""
11251141

1142+
alice: User
1143+
11261144
@classmethod
11271145
def setUpClass(cls):
11281146
super().setUpClass()

opencontractserver/tests/test_conversation_query.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def setUp(self) -> None:
4242
)
4343

4444
# Graphene client with context as self.user
45-
self.client = Client(schema, context_value=TestContext(self.user))
45+
self.graphene_client = Client(schema, context_value=TestContext(self.user))
4646

4747
# Create a test corpus and document
4848
self.corpus = Corpus.objects.create(
@@ -167,7 +167,7 @@ def test_resolve_conversations_with_corpus_id(self):
167167
corpus_global_id = to_global_id("CorpusType", self.corpus.id)
168168
variables = {"corpusId": corpus_global_id}
169169

170-
response = self.client.execute(query, variables=variables)
170+
response = self.graphene_client.execute(query, variables=variables)
171171
self.assertIsNone(
172172
response.get("errors"),
173173
f"GraphQL returned errors: {response.get('errors')}",
@@ -212,7 +212,7 @@ def test_conversations_title_search_is_case_insensitive(self):
212212
}
213213
}
214214
"""
215-
response = self.client.execute(
215+
response = self.graphene_client.execute(
216216
query, variables={"titleContains": "conversation with corpus"}
217217
)
218218
self.assertIsNone(
@@ -258,7 +258,7 @@ def test_resolve_conversations_with_document_id(self):
258258
document_global_id = to_global_id("DocumentType", self.doc.id)
259259
variables = {"documentId": document_global_id}
260260

261-
response = self.client.execute(query, variables=variables)
261+
response = self.graphene_client.execute(query, variables=variables)
262262
self.assertIsNone(
263263
response.get("errors"),
264264
f"GraphQL returned errors: {response.get('errors')}",
@@ -307,7 +307,7 @@ def test_user_cannot_see_others_conversations(self):
307307
}
308308
"""
309309

310-
response = self.client.execute(query)
310+
response = self.graphene_client.execute(query)
311311
self.assertIsNone(
312312
response.get("errors"),
313313
f"GraphQL returned errors: {response.get('errors')}",

0 commit comments

Comments
 (0)