Skip to content

Commit 071f38b

Browse files
authored
Merge pull request #1401 from Open-Source-Legal/claude/resolve-issue-1332-OxMNg
Annotate config/graphql/ resolvers and mutations (Closes #1332)
2 parents b7b4810 + 182f646 commit 071f38b

160 files changed

Lines changed: 582 additions & 484 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- **Return-type annotations across `config/graphql/` resolvers and mutations** (Issue #1332, follow-up to #1331): The largest, least-typed subtree in the backend (459 function definitions, ~4.4% return-annotation coverage at baseline) is now at 100% return-annotation coverage. Touched files include every `*_mutations.py`, every `*_queries.py`, every `*_types.py`, plus `filters.py`, `base.py`, `base_types.py`, `security.py`, `optimized_file_resolvers.py`, `permissioning/permission_annotator/{middleware,mixins,utils}.py`, and the small utility modules. No behavioral changes — annotations only.
13+
- **`mutate(...)` on `graphene.Mutation` subclasses**: typed as forward references to the enclosing class (`-> "ClassName"`). Discovered and fixed the latent bug in `config/graphql/analysis_mutations.py:179` (`DeleteAnalysisMutation.mutate`) where the success path had no `return` statement; annotation is `-> "DeleteAnalysisMutation | None"` and an explicit `return None` was added to preserve the original implicit-None behavior on success.
14+
- **`resolve_*` methods**: typed as `-> Any` by default, refined where the GraphQL field type makes the runtime return obvious (e.g. `resolve_in_use -> bool`, `resolve_datacell_count -> int`).
15+
- **`AnnotatePermissionsForReadMixin`** (`config/graphql/permissioning/permission_annotator/mixins.py`): per the issue's specific guidance, `resolve_my_permissions -> list[str]`, `resolve_is_published -> bool`, `resolve_object_shared_with -> list[dict[str, Any]]`. The pre-existing wrong annotation `list[PermissionTypes]` (an Enum, while the implementation returns plain strings) was corrected to `list[str]`. The now-unused `PermissionTypes` import was removed.
16+
- **Filter / queryset helpers** (`filter_by_*`, `text_search_method`, `get_node`, `get_queryset`, `_get_*`, etc.) typed as `-> Any` to keep the change conservative; tightening to `QuerySet[Model]` is a follow-up.
17+
- **`config/graphql/permissioning/permission_annotator/utils.py`** had a broken import (`config.graphql.permission_annotator.middleware` instead of `config.graphql.permissioning.permission_annotator.middleware`) — fixed in passing.
18+
- **`config/graphql/conversation_types.py`**: replaced `base64.binascii.Error` with a direct `binascii.Error` import (pre-existing — `base64` re-exports `binascii` at runtime but `mypy` doesn't see the re-export).
19+
- **Var-annotated additions**: `id_to_children: dict[Any, list[Any]]` in `base_types.py`, `read_only_fields: list[str]` in `serializers.py`, `this_model_permission_id_map: dict[int, str]` etc. in middleware.
20+
- **Five modules graduated from the mypy baseline** (`mypy.ini` → no longer `ignore_errors = True`): `config.graphql.base_types`, `config.graphql.conversation_types`, `config.graphql.permissioning.permission_annotator.middleware`, `config.graphql.permissioning.permission_annotator.utils`, `config.graphql.serializers`. Their entries in `docs/typing/mypy_baseline.txt` (11 lines) were also pruned. Future PRs can graduate the remaining baselined files as the structural issues they expose (custom `visible_to_user` manager method not seen by `django-stubs`, `set_permissions_for_obj_to_user` signature mismatch, mixin `_meta` access) are addressed.
21+
- **Tooling**: zero new `# type: ignore` markers; black & isort applied; `flake8 config/graphql/` clean. `mypy --config-file mypy.ini opencontractserver config` passes with the updated baseline.
1222
- **Mypy: graduated `opencontractserver/users/tasks.py` out of the baseline** (Issue #1333 follow-up): `tasks.py` was the last `opencontractserver.users` module still suppressed in `mypy.ini`. PR #1370 left it untyped because the file is only loaded when `settings.USE_AUTH0=True`, so it never failed at runtime under the test settings; the typing gap kept the package short of the issue's "all four packages at ≥80% return-annotation coverage" Done-When criterion. Added return + parameter annotations to all five Auth0 sync tasks (`get_new_auth0_token`, `apply_data_to_user`, `sync_remote_user`, `ensure_valid_auth0_token`, `get_user_details_async`), introduced a module-level docstring documenting the `USE_AUTH0` gating, and removed the `[mypy-opencontractserver.users.tasks] ignore_errors = True` section. Local `data` rebound from request body (`dict[str, str]`) to response payload (`dict[str, Any]`) was split into two distinctly-named variables (`request_data` / `payload`) so the types are unambiguous; behavior is unchanged. No callers needed updating — `config/graphql_auth0_auth/utils.py` still consumes `sync_remote_user.delay(...)` exactly as before.
1323

1424
### Fixed

config/graphql/action_queries.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
import logging
6+
from typing import Any
67

78
import graphene
89
from graphene_django.fields import DjangoConnectionField
@@ -33,7 +34,7 @@ class ActionQueryMixin:
3334
)
3435

3536
@login_required
36-
def resolve_corpus_action_templates(self, info, **kwargs):
37+
def resolve_corpus_action_templates(self, info, **kwargs) -> Any:
3738
"""Return available corpus action templates.
3839
3940
Templates are system-level and read-only — any authenticated user
@@ -58,7 +59,7 @@ def resolve_corpus_action_templates(self, info, **kwargs):
5859
)
5960

6061
@login_required
61-
def resolve_corpus_actions(self, info, **kwargs):
62+
def resolve_corpus_actions(self, info, **kwargs) -> Any:
6263
"""
6364
Resolver for corpus_actions that returns actions visible to the current user.
6465
Can be filtered by corpus_id, trigger type, and disabled status.
@@ -93,7 +94,7 @@ def resolve_corpus_actions(self, info, **kwargs):
9394
)
9495

9596
@login_required
96-
def resolve_agent_action_results(self, info, **kwargs):
97+
def resolve_agent_action_results(self, info, **kwargs) -> Any:
9798
"""
9899
Resolver for agent_action_results that returns results visible to the current user.
99100
Can be filtered by corpus_action_id, document_id, and status.
@@ -142,7 +143,7 @@ def resolve_agent_action_results(self, info, **kwargs):
142143
)
143144

144145
@login_required
145-
def resolve_corpus_action_executions(self, info, **kwargs):
146+
def resolve_corpus_action_executions(self, info, **kwargs) -> Any:
146147
"""
147148
Resolver for corpus_action_executions that returns executions visible to
148149
the current user.
@@ -220,7 +221,7 @@ def resolve_corpus_action_executions(self, info, **kwargs):
220221
)
221222

222223
@login_required
223-
def resolve_corpus_action_trail_stats(self, info, corpus_id, since=None):
224+
def resolve_corpus_action_trail_stats(self, info, corpus_id, since=None) -> Any:
224225
"""
225226
Resolver for corpus_action_trail_stats that returns aggregated statistics
226227
for corpus action executions.
@@ -291,7 +292,7 @@ def resolve_corpus_action_trail_stats(self, info, corpus_id, since=None):
291292
corpus_id=graphene.ID(required=False),
292293
)
293294

294-
def resolve_document_corpus_actions(self, info, document_id, corpus_id=None):
295+
def resolve_document_corpus_actions(self, info, document_id, corpus_id=None) -> Any:
295296
"""
296297
Resolve document actions (corpus actions, extracts, analysis rows) with proper
297298
permission filtering.

config/graphql/agent_mutations.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def mutate(
8080
avatar_url=None,
8181
corpus_id=None,
8282
is_public=True,
83-
):
83+
) -> "CreateAgentConfigurationMutation":
8484
user = info.context.user
8585

8686
try:
@@ -204,7 +204,7 @@ def mutate(
204204
avatar_url=None,
205205
is_active=None,
206206
is_public=None,
207-
):
207+
) -> "UpdateAgentConfigurationMutation":
208208
user = info.context.user
209209

210210
try:
@@ -282,7 +282,7 @@ class Arguments:
282282

283283
@login_required
284284
@graphql_ratelimit(rate=RateLimits.WRITE_LIGHT)
285-
def mutate(root, info, agent_id):
285+
def mutate(root, info, agent_id) -> "DeleteAgentConfigurationMutation":
286286
user = info.context.user
287287

288288
try:

config/graphql/agent_types.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""GraphQL type definitions for agent and action types."""
22

3+
from typing import Any
4+
35
import graphene
46
from graphene import relay
57
from graphene_django import DjangoObjectType
@@ -37,7 +39,7 @@ class Meta:
3739
"source_template__id": ["exact"],
3840
}
3941

40-
def resolve_pre_authorized_tools(self, info):
42+
def resolve_pre_authorized_tools(self, info) -> Any:
4143
"""Resolve pre_authorized_tools as a list of strings."""
4244
return self.pre_authorized_tools or []
4345

@@ -61,15 +63,15 @@ class Meta:
6163
"creator__id": ["exact"],
6264
}
6365

64-
def resolve_tools_executed(self, info):
66+
def resolve_tools_executed(self, info) -> Any:
6567
"""Resolve tools_executed as a list of JSON objects."""
6668
return self.tools_executed or []
6769

68-
def resolve_execution_metadata(self, info):
70+
def resolve_execution_metadata(self, info) -> Any:
6971
"""Resolve execution_metadata as JSON dict."""
7072
return self.execution_metadata or {}
7173

72-
def resolve_duration_seconds(self, info):
74+
def resolve_duration_seconds(self, info) -> Any:
7375
"""Resolve duration from the model property."""
7476
return self.duration_seconds
7577

@@ -100,19 +102,19 @@ class Meta:
100102
"creator__id": ["exact"],
101103
}
102104

103-
def resolve_duration_seconds(self, info):
105+
def resolve_duration_seconds(self, info) -> Any:
104106
"""Resolve duration from the model property."""
105107
return self.duration_seconds
106108

107-
def resolve_wait_time_seconds(self, info):
109+
def resolve_wait_time_seconds(self, info) -> Any:
108110
"""Resolve wait time from the model property."""
109111
return self.wait_time_seconds
110112

111-
def resolve_affected_objects(self, info):
113+
def resolve_affected_objects(self, info) -> Any:
112114
"""Resolve affected_objects as a list of JSON objects."""
113115
return self.affected_objects or []
114116

115-
def resolve_execution_metadata(self, info):
117+
def resolve_execution_metadata(self, info) -> Any:
116118
"""Resolve execution_metadata as JSON dict."""
117119
return self.execution_metadata or {}
118120

@@ -180,17 +182,17 @@ class Meta:
180182
"corpus": ["exact"],
181183
}
182184

183-
def resolve_mention_format(self, info):
185+
def resolve_mention_format(self, info) -> Any:
184186
"""Return the @ mention format for this agent."""
185187
if self.slug:
186188
return f"@agent:{self.slug}"
187189
return None
188190

189-
def resolve_available_tools(self, info):
191+
def resolve_available_tools(self, info) -> Any:
190192
"""Resolve available_tools as a list of strings, ensuring proper array type."""
191193
return self.available_tools if self.available_tools else []
192194

193-
def resolve_permission_required_tools(self, info):
195+
def resolve_permission_required_tools(self, info) -> Any:
194196
"""Resolve permission_required_tools as a list of strings, ensuring proper array type."""
195197
return self.permission_required_tools if self.permission_required_tools else []
196198

@@ -261,5 +263,5 @@ class Meta:
261263
"created",
262264
)
263265

264-
def resolve_pre_authorized_tools(self, info):
266+
def resolve_pre_authorized_tools(self, info) -> Any:
265267
return self.pre_authorized_tools or []

config/graphql/analysis_mutations.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class Arguments:
3737

3838
@user_passes_test(lambda user: user.is_superuser)
3939
@graphql_ratelimit(rate=RateLimits.ADMIN_OPERATION)
40-
def mutate(root, info, analysis_id):
40+
def mutate(root, info, analysis_id) -> "MakeAnalysisPublic":
4141

4242
try:
4343
analysis_pk = from_global_id(analysis_id)[1]
@@ -87,7 +87,7 @@ def mutate(
8787
document_id=None,
8888
corpus_id=None,
8989
analysis_input_data=None,
90-
):
90+
) -> "StartDocumentAnalysisMutation":
9191
"""
9292
Starts a document or corpus analysis using the specified analyzer.
9393
Accepts optional analysis_input_data for analyzers that need
@@ -176,7 +176,7 @@ class Arguments:
176176
id = graphene.String(required=True)
177177

178178
@login_required
179-
def mutate(root, info, id):
179+
def mutate(root, info, id) -> "DeleteAnalysisMutation | None":
180180

181181
# ok = False
182182
# message = "Could not complete"
@@ -206,3 +206,4 @@ def mutate(root, info, id):
206206

207207
# Kick off an async task to delete the analysis (as it can be very large)
208208
delete_analysis_and_annotations_task.si(analysis_pk=analysis_pk).apply_async()
209+
return None

config/graphql/annotation_mutations.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class Arguments:
4747
message = graphene.String()
4848

4949
@login_required
50-
def mutate(root, info, annotation_id):
50+
def mutate(root, info, annotation_id) -> "RemoveAnnotation":
5151
try:
5252
user = info.context.user
5353
annotation_pk = from_global_id(annotation_id)[1]
@@ -96,7 +96,7 @@ class Arguments:
9696

9797
@login_required
9898
@transaction.atomic
99-
def mutate(root, info, annotation_id, comment=None):
99+
def mutate(root, info, annotation_id, comment=None) -> "RejectAnnotation":
100100
user = info.context.user
101101
annotation_pk = from_global_id(annotation_id)[1]
102102

@@ -159,7 +159,7 @@ class Arguments:
159159

160160
@login_required
161161
@transaction.atomic
162-
def mutate(root, info, annotation_id, comment=None):
162+
def mutate(root, info, annotation_id, comment=None) -> "ApproveAnnotation":
163163
user = info.context.user
164164
annotation_pk = from_global_id(annotation_id)[1]
165165

@@ -254,7 +254,7 @@ def mutate(
254254
annotation_label_id,
255255
annotation_type,
256256
long_description=None,
257-
):
257+
) -> "AddAnnotation":
258258
corpus_pk = from_global_id(corpus_id)[1]
259259
document_pk = from_global_id(document_id)[1]
260260
label_pk = from_global_id(annotation_label_id)[1]
@@ -296,7 +296,9 @@ class Arguments:
296296
annotation = graphene.Field(AnnotationType)
297297

298298
@login_required
299-
def mutate(root, info, corpus_id, document_id, annotation_label_id):
299+
def mutate(
300+
root, info, corpus_id, document_id, annotation_label_id
301+
) -> "AddDocTypeAnnotation":
300302
annotation = None
301303
ok = False
302304

@@ -328,7 +330,7 @@ class Arguments:
328330
message = graphene.String()
329331

330332
@login_required
331-
def mutate(root, info, relationship_id):
333+
def mutate(root, info, relationship_id) -> "RemoveRelationship":
332334
try:
333335
user = info.context.user
334336
relationship_pk = from_global_id(relationship_id)[1]
@@ -400,7 +402,7 @@ def mutate(
400402
relationship_label_id,
401403
corpus_id,
402404
document_id,
403-
):
405+
) -> "AddRelationship":
404406
try:
405407
source_pks = list(
406408
map(lambda graphene_id: from_global_id(graphene_id)[1], source_ids)
@@ -479,7 +481,7 @@ class Arguments:
479481
message = graphene.String()
480482

481483
@login_required
482-
def mutate(root, info, relationship_ids):
484+
def mutate(root, info, relationship_ids) -> "RemoveRelationships":
483485
user = info.context.user
484486
for graphene_id in relationship_ids:
485487
pk = from_global_id(graphene_id)[1]
@@ -542,7 +544,7 @@ def mutate(
542544
add_target_ids=None,
543545
remove_source_ids=None,
544546
remove_target_ids=None,
545-
):
547+
) -> "UpdateRelationship":
546548
try:
547549
relationship_pk = from_global_id(relationship_id)[1]
548550
relationship = Relationship.objects.get(pk=relationship_pk)
@@ -662,7 +664,7 @@ class Arguments:
662664
message = graphene.String()
663665

664666
@login_required
665-
def mutate(root, info, relationships):
667+
def mutate(root, info, relationships) -> "UpdateRelations":
666668
user = info.context.user
667669
for relationship in relationships:
668670
pk = from_global_id(relationship["id"])[1]
@@ -728,7 +730,7 @@ class Arguments:
728730
version = graphene.Int(description="The new version number after update")
729731

730732
@login_required
731-
def mutate(root, info, note_id, new_content, title=None):
733+
def mutate(root, info, note_id, new_content, title=None) -> "UpdateNote":
732734
from opencontractserver.annotations.models import Note
733735

734736
try:
@@ -826,7 +828,9 @@ class Arguments:
826828
obj = graphene.Field(NoteType)
827829

828830
@login_required
829-
def mutate(root, info, document_id, title, content, corpus_id=None, parent_id=None):
831+
def mutate(
832+
root, info, document_id, title, content, corpus_id=None, parent_id=None
833+
) -> "CreateNote":
830834
from opencontractserver.annotations.models import Note
831835
from opencontractserver.corpuses.models import Corpus
832836
from opencontractserver.documents.models import Document

0 commit comments

Comments
 (0)