Skip to content

Commit 0954477

Browse files
authored
Merge pull request #78 from eadwinCode/context_var
switched to context var
2 parents 0cc19d7 + 7acd0e0 commit 0954477

File tree

11 files changed

+84
-79
lines changed

11 files changed

+84
-79
lines changed

Makefile

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,27 @@ clean: ## Removing cached python compiled files
1010
find . -name \*~ | xargs rm -fv
1111
find . -name __pycache__ | xargs rm -rfv
1212

13-
install: ## Install dependencies
14-
make clean
13+
install:clean ## Install dependencies
1514
flit install --deps develop --symlink
1615
pre-commit install -f
1716

18-
lint: ## Run code linters
17+
lint:fmt ## Run code linters
1918
make clean
2019
black --check ninja_extra tests
2120
ruff check ninja_extra tests
2221
mypy ninja_extra
2322

24-
fmt format: ## Run code formatters
25-
make clean
23+
fmt format:clean ## Run code formatters
2624
black ninja_extra tests
2725
ruff check --fix ninja_extra tests
2826

2927

30-
test: ## Run tests
31-
make clean
28+
test:clean ## Run tests
3229
pytest .
3330

34-
test-cov: ## Run tests with coverage
31+
test-cov:clean ## Run tests with coverage
3532
make clean
3633
pytest --cov=ninja_extra --cov-report term-missing tests
3734

38-
doc-deploy: ## Run Deploy Documentation
39-
make clean
35+
doc-deploy:clean ## Run Deploy Documentation
4036
mkdocs gh-deploy --force

mypy.ini

Lines changed: 0 additions & 22 deletions
This file was deleted.

ninja_extra/constants.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
import contextvars
2+
import typing as t
3+
4+
if t.TYPE_CHECKING: # pragma: no cover
5+
from ninja_extra.controllers.route.context import RouteContext
6+
7+
18
POST = "POST"
29
PUT = "PUT"
310
PATCH = "PATCH"
@@ -9,3 +16,8 @@
916
ROUTE_METHODS = [POST, PUT, PATCH, DELETE, GET, HEAD, OPTIONS, TRACE]
1017
THROTTLED_FUNCTION = "__throttled_endpoint__"
1118
ROUTE_FUNCTION = "__route_function__"
19+
20+
ROUTE_CONTEXT_VAR: contextvars.ContextVar[
21+
t.Optional["RouteContext"]
22+
] = contextvars.ContextVar("ROUTE_CONTEXT_VAR")
23+
ROUTE_CONTEXT_VAR.set(None)

ninja_extra/controllers/base.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,7 @@ def _get_permissions(self) -> Iterable[BasePermission]:
166166
"""
167167
Instantiates and returns the list of permissions that this view requires.
168168
"""
169-
if not self.context:
170-
return
169+
assert self.context
171170

172171
for permission_class in self.context.permission_classes:
173172
if isinstance(permission_class, (type, OperationHolderMixin)):

ninja_extra/exceptions.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
DRF Exceptions
33
"""
44
import math
5-
from typing import Any, Dict, List, Optional, Type, Union, cast, no_type_check
5+
from typing import Any, Dict, List, Optional, Type, Union, no_type_check
66

77
from django.http import HttpRequest, HttpResponse, JsonResponse
88
from django.utils.encoding import force_str
@@ -104,7 +104,7 @@ def __init__(
104104
code: Optional[Union[str, int]] = None,
105105
) -> None:
106106
if detail is None:
107-
detail = cast(str, self.default_detail)
107+
detail = force_str(self.default_detail)
108108
if code is None:
109109
code = self.default_code
110110

@@ -149,7 +149,7 @@ def __init__(
149149
code: Optional[Union[str, int]] = None,
150150
):
151151
if detail is None:
152-
detail = cast(str, self.default_detail)
152+
detail = force_str(self.default_detail)
153153
if code is None:
154154
code = self.default_code
155155

ninja_extra/modules.py

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,30 @@
11
import threading
2-
from typing import Any, Optional, cast
2+
from typing import Optional
33

4-
from django.apps import apps
54
from django.conf import Settings, settings
6-
from django.dispatch import receiver
75
from injector import Binder, Module, singleton
86

97
from ninja_extra.controllers.route.context import RouteContext
108

11-
from .signals import route_context_finished, route_context_started
9+
from .constants import ROUTE_CONTEXT_VAR
1210

13-
14-
@receiver([route_context_started, route_context_finished], sender=RouteContext)
15-
def route_context_handler(
16-
*args: Any, route_context: Optional[RouteContext] = None, **kwargs: Any
17-
) -> None:
18-
app = cast(Any, apps.get_app_config("ninja_extra"))
19-
app.ninja_extra_module.set_route_context(route_context)
11+
# @receiver([route_context_started, route_context_finished], sender=RouteContext)
12+
# def route_context_handler(
13+
# *args: Any, route_context: Optional[RouteContext] = None, **kwargs: Any
14+
# ) -> None:
15+
# app = cast(Any, apps.get_app_config("ninja_extra"))
16+
# app.ninja_extra_module.set_route_context(route_context)
2017

2118

2219
class NinjaExtraModule(Module):
2320
def __init__(self) -> None:
2421
self._local = threading.local()
2522

2623
def set_route_context(self, route_context: RouteContext) -> None:
27-
self._local.route_context = route_context
24+
ROUTE_CONTEXT_VAR.set(route_context)
2825

2926
def get_route_context(self) -> Optional[RouteContext]:
30-
try:
31-
return self._local.route_context # type:ignore
32-
except AttributeError: # pragma: no cover
33-
return None
27+
return ROUTE_CONTEXT_VAR.get()
3428

3529
def configure(self, binder: Binder) -> None:
3630
binder.bind(Settings, to=settings, scope=singleton) # type: ignore

ninja_extra/operation.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@
3535
from ninja.utils import check_csrf
3636

3737
from ninja_extra.compatible import asynccontextmanager
38+
from ninja_extra.constants import ROUTE_CONTEXT_VAR
3839
from ninja_extra.exceptions import APIException
3940
from ninja_extra.helper import get_function_name
4041
from ninja_extra.logger import request_logger
41-
from ninja_extra.signals import route_context_finished, route_context_started
42+
43+
# from ninja_extra.signals import route_context_finished, route_context_started
4244
from ninja_extra.types import PermissionType
4345

4446
from .controllers.route.context import RouteContext, get_route_execution_context
@@ -153,7 +155,7 @@ def _prep_run(
153155
request, temporal_response=temporal_response, **kw
154156
)
155157
# send route_context_started signal
156-
route_context_started.send(RouteContext, route_context=context)
158+
ROUTE_CONTEXT_VAR.set(context)
157159

158160
yield context
159161
self._log_action(
@@ -174,7 +176,7 @@ def _prep_run(
174176
raise e
175177
finally:
176178
# send route_context_finished signal
177-
route_context_finished.send(RouteContext, route_context=None)
179+
ROUTE_CONTEXT_VAR.set(None)
178180

179181
def run(self, request: HttpRequest, **kw: Any) -> HttpResponseBase:
180182
error = self._run_checks(request)
@@ -265,7 +267,7 @@ def _prep_run(
265267
request, temporal_response=temporal_response, **kw
266268
)
267269
# send route_context_started signal
268-
route_context_started.send(RouteContext, route_context=context)
270+
ROUTE_CONTEXT_VAR.set(context)
269271

270272
yield context
271273
self._log_action(
@@ -286,7 +288,7 @@ def _prep_run(
286288
raise e
287289
finally:
288290
# send route_context_finished signal
289-
route_context_finished.send(RouteContext, route_context=None)
291+
ROUTE_CONTEXT_VAR.set(None)
290292

291293
def run(self, request: HttpRequest, **kw: Any) -> HttpResponseBase:
292294
error = self._run_checks(request)
@@ -363,7 +365,7 @@ async def _prep_run( # type:ignore
363365
start_time = time.time()
364366
context = self.get_execution_context(request, **kw)
365367
# send route_context_started signal
366-
route_context_started.send(RouteContext, route_context=context)
368+
ROUTE_CONTEXT_VAR.set(context)
367369

368370
yield context
369371
self._log_action(
@@ -384,7 +386,7 @@ async def _prep_run( # type:ignore
384386
raise e
385387
finally:
386388
# send route_context_finished signal
387-
route_context_finished.send(RouteContext, route_context=None)
389+
ROUTE_CONTEXT_VAR.set(None)
388390

389391
async def run(self, request: HttpRequest, **kw: Any) -> HttpResponseBase: # type: ignore
390392
error = await self._run_checks(request)
@@ -413,7 +415,7 @@ async def _prep_run( # type:ignore
413415
start_time = time.time()
414416
context = self.get_execution_context(request, **kw)
415417
# send route_context_started signal
416-
route_context_started.send(RouteContext, route_context=context)
418+
ROUTE_CONTEXT_VAR.set(context)
417419

418420
yield context
419421
self._log_action(
@@ -434,7 +436,7 @@ async def _prep_run( # type:ignore
434436
raise e
435437
finally:
436438
# send route_context_finished signal
437-
route_context_finished.send(RouteContext, route_context=None)
439+
ROUTE_CONTEXT_VAR.set(None)
438440

439441
async def run(self, request: HttpRequest, **kw: Any) -> HttpResponseBase: # type: ignore
440442
error = await self._run_checks(request)

ninja_extra/signals.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from django.dispatch import Signal
1+
from django.dispatch import Signal # pragma: no cover
22

3-
route_context_started = Signal()
4-
route_context_finished = Signal()
3+
route_context_started = Signal() # pragma: no cover
4+
route_context_finished = Signal() # pragma: no cover

pyproject.toml

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ classifiers = [
2222
"Intended Audience :: Developers",
2323
"License :: OSI Approved :: MIT License",
2424
"Programming Language :: Python :: 3",
25-
"Programming Language :: Python :: 3.6",
26-
"Programming Language :: Python :: 3.7",
2725
"Programming Language :: Python :: 3.8",
2826
"Programming Language :: Python :: 3.9",
2927
"Programming Language :: Python :: 3.10",
@@ -62,7 +60,7 @@ test = [
6260
"pytest-cov",
6361
"pytest-django",
6462
"pytest-asyncio",
65-
"mypy == 1.4.1",
63+
"mypy == 1.5.1",
6664
"ruff ==0.0.275",
6765
"black == 23.7.0",
6866
"injector >= 0.19.0",
@@ -101,3 +99,25 @@ ignore = [
10199

102100
[tool.ruff.isort]
103101
known-third-party = ["django-ninja-extra", "Django", "injector", "pydantic"]
102+
103+
[tool.mypy]
104+
show_column_numbers = true
105+
106+
follow_imports = 'normal'
107+
ignore_missing_imports = true
108+
109+
# be strict
110+
disallow_untyped_calls = true
111+
warn_return_any = true
112+
strict_optional = true
113+
warn_no_return = true
114+
warn_redundant_casts = true
115+
warn_unused_ignores = true
116+
117+
disallow_untyped_defs = true
118+
check_untyped_defs = true
119+
no_implicit_reexport = true
120+
121+
[[tool.mypy.overrides]]
122+
module = "ninja_extra.compatible.*"
123+
ignore_errors = true

tests/test_async_auth.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import base64
2+
13
import django
24
import pytest
35
from django.conf import settings
@@ -14,6 +16,8 @@
1416
async_django_auth,
1517
)
1618

19+
user_secret = base64.b64encode("admin:secret".encode("utf-8")).decode()
20+
1721

1822
async def callable_auth(request):
1923
return request.GET.get("auth")
@@ -182,13 +186,13 @@ async def test_csrf_on():
182186
("/basic", {}, 401, BODY_UNAUTHORIZED_DEFAULT),
183187
(
184188
"/basic",
185-
{"headers": {"Authorization": "Basic YWRtaW46c2VjcmV0"}},
189+
{"headers": {"Authorization": f"Basic {user_secret}"}},
186190
200,
187191
{"auth": "admin"},
188192
),
189193
(
190194
"/basic",
191-
{"headers": {"Authorization": "YWRtaW46c2VjcmV0"}},
195+
{"headers": {"Authorization": f"{user_secret}"}},
192196
200,
193197
{"auth": "admin"},
194198
),

0 commit comments

Comments
 (0)