Skip to content

Commit b7b4810

Browse files
authored
Merge pull request #1396 from Open-Source-Legal/claude/resolve-issue-1333-QwoCf
Type annotate users/tasks.py and graduate from mypy baseline (#1333)
2 parents 98a0d1a + d3a5db1 commit b7b4810

3 files changed

Lines changed: 31 additions & 20 deletions

File tree

CHANGELOG.md

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

88
## [Unreleased]
99

10+
### Added
11+
12+
- **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.
13+
1014
### Fixed
1115

1216
- **`test_superuser_sees_all_queryset` miscounts personal corpuses by 1** (Issue #1394, `opencontractserver/tests/test_visibility_managers.py`, `opencontractserver/tests/test_resolvers.py`): Two `VisibleToUserTests.test_superuser_sees_all_queryset` cases asserted that `Corpus.objects.visible_to_user(superuser).count() == 4` (public + private + 2 personal), but the actual count is 5 because the test DB starts with a pre-existing personal corpus owned by django-guardian's `AnonymousUser` (created during fixture setup before/around the username-based skip in `opencontractserver/users/signals.py::user_created_signal`). The assertion is now scoped to corpuses created by the test's two users (`creator__in=[self.user, self.superuser]`), making it resilient to any fixture-level corpuses that exist at test DB init time. Production code is unchanged.

mypy.ini

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,10 +1095,7 @@ ignore_errors = True
10951095
[mypy-opencontractserver.tests.websocket.test_unified_agent_consumer]
10961096
ignore_errors = True
10971097

1098-
# --- opencontractserver.users (1 file) ---
1099-
1100-
[mypy-opencontractserver.users.tasks]
1101-
ignore_errors = True
1098+
# --- opencontractserver.users ---
11021099

11031100
# ``opencontractserver.users.models.User`` defines reverse relations to
11041101
# ``opencontractserver.corpuses.models.Corpus``, ``.CorpusFolder``, and

opencontractserver/users/tasks.py

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1+
"""Celery tasks for synchronising user data with Auth0.
2+
3+
These tasks are defined only when ``settings.USE_AUTH0`` is enabled — they
4+
fetch a Machine-to-Machine token, look up the remote profile via the Auth0
5+
Management API, and copy the result onto the local :class:`User` row.
6+
"""
7+
18
import datetime
29
import json
310
import logging
11+
from typing import Any, Optional
412

513
import requests
614
from celery import chain
15+
from celery.result import AsyncResult
716
from django.conf import settings
817
from django.contrib.auth import get_user_model
918

@@ -21,34 +30,34 @@
2130
if settings.USE_AUTH0:
2231

2332
@celery_app.task()
24-
def get_new_auth0_token():
33+
def get_new_auth0_token() -> Optional[str]:
2534

2635
# print("get_new_auth0_token")
2736
url = f"https://{auth0_settings.AUTH0_DOMAIN}/oauth/token"
2837
# print(url)
2938

30-
headers = {"content-type": "application/json"}
39+
headers: dict[str, str] = {"content-type": "application/json"}
3140
# print(headers)
3241

33-
data = {
42+
request_data: dict[str, str] = {
3443
"grant_type": auth0_settings.AUTH0_M2M_MANAGEMENT_GRANT_TYPE,
3544
"client_id": auth0_settings.AUTH0_M2M_MANAGEMENT_API_ID,
3645
"client_secret": auth0_settings.AUTH0_M2M_MANAGEMENT_API_SECRET,
3746
"audience": f"https://{auth0_settings.AUTH0_DOMAIN}/api/v2/",
3847
}
39-
# print(f"Machine-2-Machine Request data: {data}")
48+
# print(f"Machine-2-Machine Request data: {request_data}")
4049

41-
response = requests.post(url, headers=headers, json=data)
50+
response = requests.post(url, headers=headers, json=request_data)
4251

4352
# print("Auth0 Response:")
4453
# print(response.status_code)
4554
# print(response.text)
4655

4756
if response.status_code == 200:
48-
data = json.loads(response.text)
49-
# print(data)
50-
access_token = data["access_token"]
51-
expires_in = data["expires_in"]
57+
payload: dict[str, Any] = json.loads(response.text)
58+
# print(payload)
59+
access_token: str = payload["access_token"]
60+
expires_in: int = payload["expires_in"]
5261

5362
newToken = Auth0APIToken()
5463
newToken.token = access_token
@@ -60,11 +69,11 @@ def get_new_auth0_token():
6069

6170
return newToken.token
6271

63-
else:
64-
logger.error("Error retrieving access token to Auth0.")
72+
logger.error("Error retrieving access token to Auth0.")
73+
return None
6574

6675
@celery_app.task()
67-
def apply_data_to_user(data, userPk):
76+
def apply_data_to_user(data: dict[str, Any], userPk: str) -> None:
6877

6978
# print(f"apply_data_to_user() - userPk is: {userPk}\nData: {data}")
7079

@@ -103,7 +112,7 @@ def apply_data_to_user(data, userPk):
103112
)
104113

105114
@celery_app.task()
106-
def sync_remote_user(user_pk):
115+
def sync_remote_user(user_pk: str) -> AsyncResult:
107116

108117
refresh = False
109118
tokens = Auth0APIToken.objects.all()
@@ -133,7 +142,7 @@ def sync_remote_user(user_pk):
133142
return data.apply_async()
134143

135144
@celery_app.task()
136-
def ensure_valid_auth0_token():
145+
def ensure_valid_auth0_token() -> Optional[str]:
137146

138147
tokens = Auth0APIToken.objects.all()
139148

@@ -147,6 +156,7 @@ def ensure_valid_auth0_token():
147156
for tok in tokens:
148157
tok.delete()
149158
return get_new_auth0_token.delay().get()
159+
return None
150160
else:
151161
if tokens[0].expiration_Date < datetime.datetime.now(datetime.timezone.utc):
152162
# print("Token has expired. Refetching from Auth0")
@@ -157,8 +167,8 @@ def ensure_valid_auth0_token():
157167
return tokens[0].token
158168

159169
@celery_app.task
160-
def get_user_details_async(token, auth0_Id):
161-
headers = {
170+
def get_user_details_async(token: str, auth0_Id: str) -> dict[str, Any]:
171+
headers: dict[str, str] = {
162172
"Authorization": f"Bearer {token}",
163173
}
164174
url = f"https://{auth0_settings.AUTH0_DOMAIN}/api/v2/users/{auth0_Id}"

0 commit comments

Comments
 (0)