django-structlog 8.1.0__tar.gz → 9.0.0.dev1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {django_structlog-8.1.0/django_structlog.egg-info → django_structlog-9.0.0.dev1}/PKG-INFO +5 -3
  2. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/django_structlog/__init__.py +1 -1
  3. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/django_structlog/app_settings.py +4 -4
  4. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/django_structlog/apps.py +1 -1
  5. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/django_structlog/celery/receivers.py +75 -32
  6. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/django_structlog/celery/steps.py +3 -1
  7. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/django_structlog/commands.py +14 -5
  8. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/django_structlog/middlewares/request.py +42 -15
  9. django_structlog-9.0.0.dev1/django_structlog/py.typed +0 -0
  10. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1/django_structlog.egg-info}/PKG-INFO +5 -3
  11. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/django_structlog.egg-info/SOURCES.txt +1 -0
  12. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/pyproject.toml +32 -8
  13. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/LICENSE.rst +0 -0
  14. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/MANIFEST.in +0 -0
  15. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/README.rst +0 -0
  16. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/django_structlog/celery/__init__.py +0 -0
  17. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/django_structlog/celery/signals.py +0 -0
  18. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/django_structlog/middlewares/__init__.py +0 -0
  19. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/django_structlog/signals.py +0 -0
  20. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/django_structlog.egg-info/dependency_links.txt +0 -0
  21. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/django_structlog.egg-info/requires.txt +0 -0
  22. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/django_structlog.egg-info/top_level.txt +0 -0
  23. {django_structlog-8.1.0 → django_structlog-9.0.0.dev1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-structlog
3
- Version: 8.1.0
3
+ Version: 9.0.0.dev1
4
4
  Summary: Structured Logging for Django
5
5
  Author-email: Jules Robichaud-Gagnon <j.robichaudg+pypi@gmail.com>
6
6
  License: MIT
@@ -13,17 +13,19 @@ Classifier: Development Status :: 5 - Production/Stable
13
13
  Classifier: Framework :: Django
14
14
  Classifier: Framework :: Django :: 4.2
15
15
  Classifier: Framework :: Django :: 5.0
16
+ Classifier: Framework :: Django :: 5.1
16
17
  Classifier: Programming Language :: Python :: 3
17
18
  Classifier: Programming Language :: Python :: 3 :: Only
18
- Classifier: Programming Language :: Python :: 3.8
19
19
  Classifier: Programming Language :: Python :: 3.9
20
20
  Classifier: Programming Language :: Python :: 3.10
21
21
  Classifier: Programming Language :: Python :: 3.11
22
22
  Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
23
24
  Classifier: Topic :: System :: Logging
24
25
  Classifier: License :: OSI Approved :: MIT License
25
26
  Classifier: Operating System :: OS Independent
26
- Requires-Python: >=3.8
27
+ Classifier: Typing :: Typed
28
+ Requires-Python: >=3.9
27
29
  Description-Content-Type: text/x-rst
28
30
  License-File: LICENSE.rst
29
31
  Requires-Dist: django>=4.2
@@ -3,6 +3,6 @@
3
3
 
4
4
  name = "django_structlog"
5
5
 
6
- VERSION = (8, 1, 0)
6
+ VERSION = (9, 0, 0, "dev1")
7
7
 
8
8
  __version__ = ".".join(str(v) for v in VERSION)
@@ -8,19 +8,19 @@ class AppSettings:
8
8
  PREFIX = "DJANGO_STRUCTLOG_"
9
9
 
10
10
  @property
11
- def CELERY_ENABLED(self):
11
+ def CELERY_ENABLED(self) -> bool:
12
12
  return getattr(settings, self.PREFIX + "CELERY_ENABLED", False)
13
13
 
14
14
  @property
15
- def STATUS_4XX_LOG_LEVEL(self):
15
+ def STATUS_4XX_LOG_LEVEL(self) -> int:
16
16
  return getattr(settings, self.PREFIX + "STATUS_4XX_LOG_LEVEL", logging.WARNING)
17
17
 
18
18
  @property
19
- def COMMAND_LOGGING_ENABLED(self):
19
+ def COMMAND_LOGGING_ENABLED(self) -> bool:
20
20
  return getattr(settings, self.PREFIX + "COMMAND_LOGGING_ENABLED", False)
21
21
 
22
22
  @property
23
- def USER_ID_FIELD(self):
23
+ def USER_ID_FIELD(self) -> str:
24
24
  return getattr(settings, self.PREFIX + "USER_ID_FIELD", "pk")
25
25
 
26
26
 
@@ -6,7 +6,7 @@ from .app_settings import app_settings
6
6
  class DjangoStructLogConfig(AppConfig):
7
7
  name = "django_structlog"
8
8
 
9
- def ready(self):
9
+ def ready(self) -> None:
10
10
  if app_settings.CELERY_ENABLED:
11
11
  from .celery.receivers import CeleryReceiver
12
12
 
@@ -1,3 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Type, Any, Optional, cast, TYPE_CHECKING
4
+
1
5
  import structlog
2
6
  from celery import current_app
3
7
  from celery.signals import (
@@ -14,23 +18,27 @@ from celery.signals import (
14
18
 
15
19
  from . import signals
16
20
 
21
+ if TYPE_CHECKING:
22
+ from types import TracebackType
17
23
 
18
24
  logger = structlog.getLogger(__name__)
19
25
 
20
26
 
21
27
  class CeleryReceiver:
22
- def __init__(self):
28
+ _priority: Optional[str]
29
+
30
+ def __init__(self) -> None:
23
31
  self._priority = None
24
32
 
25
33
  def receiver_before_task_publish(
26
34
  self,
27
- sender=None,
28
- headers=None,
29
- body=None,
30
- properties=None,
31
- routing_key=None,
32
- **kwargs,
33
- ):
35
+ sender: Optional[Type[Any]] = None,
36
+ headers: Optional[dict[str, Any]] = None,
37
+ body: Optional[dict[str, str]] = None,
38
+ properties: Optional[dict[str, Optional[str]]] = None,
39
+ routing_key: Optional[str] = None,
40
+ **kwargs: dict[str, str],
41
+ ) -> None:
34
42
  if current_app.conf.task_protocol < 2:
35
43
  return
36
44
 
@@ -46,12 +54,17 @@ class CeleryReceiver:
46
54
  )
47
55
  if properties:
48
56
  self._priority = properties.get("priority", None)
49
-
50
- headers["__django_structlog__"] = context
57
+ if headers is not None:
58
+ headers["__django_structlog__"] = context
51
59
 
52
60
  def receiver_after_task_publish(
53
- self, sender=None, headers=None, body=None, routing_key=None, **kwargs
54
- ):
61
+ self,
62
+ sender: Optional[Type[Any]] = None,
63
+ headers: Optional[dict[str, Optional[str]]] = None,
64
+ body: Optional[dict[str, Optional[str]]] = None,
65
+ routing_key: Optional[str] = None,
66
+ **kwargs: Any,
67
+ ) -> None:
55
68
  properties = {}
56
69
  if self._priority is not None:
57
70
  properties["priority"] = self._priority
@@ -59,13 +72,23 @@ class CeleryReceiver:
59
72
 
60
73
  logger.info(
61
74
  "task_enqueued",
62
- child_task_id=headers.get("id") if headers else body.get("id"),
63
- child_task_name=headers.get("task") if headers else body.get("task"),
75
+ child_task_id=(
76
+ headers.get("id")
77
+ if headers
78
+ else cast(dict[str, Optional[str]], body).get("id")
79
+ ),
80
+ child_task_name=(
81
+ headers.get("task")
82
+ if headers
83
+ else cast(dict[str, Optional[str]], body).get("task")
84
+ ),
64
85
  routing_key=routing_key,
65
86
  **properties,
66
87
  )
67
88
 
68
- def receiver_task_prerun(self, task_id, task, *args, **kwargs):
89
+ def receiver_task_prerun(
90
+ self, task_id: str, task: Any, *args: Any, **kwargs: Any
91
+ ) -> None:
69
92
  structlog.contextvars.clear_contextvars()
70
93
  structlog.contextvars.bind_contextvars(task_id=task_id)
71
94
  metadata = getattr(task.request, "__django_structlog__", {})
@@ -75,10 +98,18 @@ class CeleryReceiver:
75
98
  )
76
99
  logger.info("task_started", task=task.name)
77
100
 
78
- def receiver_task_retry(self, request=None, reason=None, einfo=None, **kwargs):
101
+ def receiver_task_retry(
102
+ self,
103
+ request: Optional[Any] = None,
104
+ reason: Optional[str] = None,
105
+ einfo: Optional[Any] = None,
106
+ **kwargs: Any,
107
+ ) -> None:
79
108
  logger.warning("task_retrying", reason=reason)
80
109
 
81
- def receiver_task_success(self, result=None, **kwargs):
110
+ def receiver_task_success(
111
+ self, result: Optional[str] = None, **kwargs: Any
112
+ ) -> None:
82
113
  signals.pre_task_succeeded.send(
83
114
  sender=self.receiver_task_success, logger=logger, result=result
84
115
  )
@@ -86,14 +117,14 @@ class CeleryReceiver:
86
117
 
87
118
  def receiver_task_failure(
88
119
  self,
89
- task_id=None,
90
- exception=None,
91
- traceback=None,
92
- einfo=None,
93
- sender=None,
94
- *args,
95
- **kwargs,
96
- ):
120
+ task_id: Optional[str] = None,
121
+ exception: Optional[Exception] = None,
122
+ traceback: Optional[TracebackType] = None,
123
+ einfo: Optional[Any] = None,
124
+ sender: Optional[Type[Any]] = None,
125
+ *args: Any,
126
+ **kwargs: Any,
127
+ ) -> None:
97
128
  throws = getattr(sender, "throws", ())
98
129
  if isinstance(exception, throws):
99
130
  logger.info(
@@ -108,8 +139,13 @@ class CeleryReceiver:
108
139
  )
109
140
 
110
141
  def receiver_task_revoked(
111
- self, request=None, terminated=None, signum=None, expired=None, **kwargs
112
- ):
142
+ self,
143
+ request: Any,
144
+ terminated: Optional[bool] = None,
145
+ signum: Optional[Any] = None,
146
+ expired: Optional[Any] = None,
147
+ **kwargs: Any,
148
+ ) -> None:
113
149
  metadata = getattr(request, "__django_structlog__", {}).copy()
114
150
  metadata["task_id"] = request.id
115
151
  metadata["task"] = request.task
@@ -124,24 +160,31 @@ class CeleryReceiver:
124
160
  )
125
161
 
126
162
  def receiver_task_unknown(
127
- self, message=None, exc=None, name=None, id=None, **kwargs
128
- ):
163
+ self,
164
+ message: Optional[str] = None,
165
+ exc: Optional[Exception] = None,
166
+ name: Optional[str] = None,
167
+ id: Optional[str] = None,
168
+ **kwargs: Any,
169
+ ) -> None:
129
170
  logger.error(
130
171
  "task_not_found",
131
172
  task=name,
132
173
  task_id=id,
133
174
  )
134
175
 
135
- def receiver_task_rejected(self, message=None, exc=None, **kwargs):
176
+ def receiver_task_rejected(
177
+ self, message: Any, exc: Optional[Exception] = None, **kwargs: Any
178
+ ) -> None:
136
179
  logger.exception(
137
180
  "task_rejected", task_id=message.properties.get("correlation_id")
138
181
  )
139
182
 
140
- def connect_signals(self):
183
+ def connect_signals(self) -> None:
141
184
  before_task_publish.connect(self.receiver_before_task_publish)
142
185
  after_task_publish.connect(self.receiver_after_task_publish)
143
186
 
144
- def connect_worker_signals(self):
187
+ def connect_worker_signals(self) -> None:
145
188
  before_task_publish.connect(self.receiver_before_task_publish)
146
189
  after_task_publish.connect(self.receiver_after_task_publish)
147
190
  task_prerun.connect(self.receiver_task_prerun)
@@ -1,3 +1,5 @@
1
+ from typing import Any
2
+
1
3
  from celery import bootsteps
2
4
 
3
5
  from .receivers import CeleryReceiver
@@ -14,7 +16,7 @@ class DjangoStructLogInitStep(bootsteps.Step):
14
16
 
15
17
  """
16
18
 
17
- def __init__(self, parent, **kwargs):
19
+ def __init__(self, parent: Any, **kwargs: Any) -> None:
18
20
  super().__init__(parent, **kwargs)
19
21
  self.receiver = CeleryReceiver()
20
22
  self.receiver.connect_worker_signals()
@@ -1,16 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import List, Tuple, Mapping, Any, Type, TYPE_CHECKING
4
+
1
5
  import structlog
2
6
  import uuid
3
7
 
4
8
  from django_extensions.management.signals import pre_command, post_command
5
9
 
10
+ if TYPE_CHECKING:
11
+ import contextvars
12
+
6
13
  logger = structlog.getLogger(__name__)
7
14
 
8
15
 
9
16
  class DjangoCommandReceiver:
10
- def __init__(self):
11
- self.stack = []
17
+ def __init__(self) -> None:
18
+ self.stack: List[Tuple[str, Mapping[str, contextvars.Token[Any]]]] = []
12
19
 
13
- def pre_receiver(self, sender, *args, **kwargs):
20
+ def pre_receiver(self, sender: Type[Any], *args: Any, **kwargs: Any) -> None:
14
21
  command_id = str(uuid.uuid4())
15
22
  if len(self.stack):
16
23
  parent_command_id, _ = self.stack[-1]
@@ -26,13 +33,15 @@ class DjangoCommandReceiver:
26
33
  command_name=sender.__module__.replace(".management.commands", ""),
27
34
  )
28
35
 
29
- def post_receiver(self, sender, outcome, *args, **kwargs):
36
+ def post_receiver(
37
+ self, sender: Type[Any], outcome: str, *args: Any, **kwargs: Any
38
+ ) -> None:
30
39
  logger.info("command_finished")
31
40
 
32
41
  if len(self.stack): # pragma: no branch
33
42
  command_id, tokens = self.stack.pop()
34
43
  structlog.contextvars.reset_contextvars(**tokens)
35
44
 
36
- def connect_signals(self):
45
+ def connect_signals(self) -> None:
37
46
  pre_command.connect(self.pre_receiver)
38
47
  post_command.connect(self.post_receiver)
@@ -1,6 +1,19 @@
1
+ from __future__ import annotations
2
+
1
3
  import asyncio
2
4
  import logging
3
5
  import uuid
6
+ from typing import (
7
+ Any,
8
+ Generator,
9
+ AsyncGenerator,
10
+ Callable,
11
+ Union,
12
+ Awaitable,
13
+ cast,
14
+ Iterator,
15
+ TYPE_CHECKING,
16
+ )
4
17
 
5
18
  import structlog
6
19
  from asgiref.sync import iscoroutinefunction, markcoroutinefunction
@@ -11,17 +24,22 @@ from asgiref import sync
11
24
  from .. import signals
12
25
  from ..app_settings import app_settings
13
26
 
27
+ if TYPE_CHECKING:
28
+ from django.http import HttpRequest, HttpResponse
29
+
14
30
  logger = structlog.getLogger(__name__)
15
31
 
16
32
 
17
- def get_request_header(request, header_key, meta_key):
33
+ def get_request_header(request: HttpRequest, header_key: str, meta_key: str) -> Any:
18
34
  if hasattr(request, "headers"):
19
35
  return request.headers.get(header_key)
20
36
 
21
37
  return request.META.get(meta_key)
22
38
 
23
39
 
24
- def sync_streaming_content_wrapper(streaming_content, context):
40
+ def sync_streaming_content_wrapper(
41
+ streaming_content: Any, context: Any
42
+ ) -> Generator[Any, None, None]:
25
43
  with structlog.contextvars.bound_contextvars(**context):
26
44
  logger.info("streaming_started")
27
45
  try:
@@ -33,7 +51,9 @@ def sync_streaming_content_wrapper(streaming_content, context):
33
51
  logger.info("streaming_finished")
34
52
 
35
53
 
36
- async def async_streaming_content_wrapper(streaming_content, context):
54
+ async def async_streaming_content_wrapper(
55
+ streaming_content: Any, context: Any
56
+ ) -> AsyncGenerator[Any, None]:
37
57
  with structlog.contextvars.bound_contextvars(**context):
38
58
  logger.info("streaming_started")
39
59
  try:
@@ -61,30 +81,37 @@ class RequestMiddleware:
61
81
  sync_capable = True
62
82
  async_capable = True
63
83
 
64
- def __init__(self, get_response):
84
+ def __init__(
85
+ self,
86
+ get_response: Callable[
87
+ [HttpRequest], Union[HttpResponse, Awaitable[HttpResponse]]
88
+ ],
89
+ ) -> None:
65
90
  self.get_response = get_response
66
91
  if iscoroutinefunction(self.get_response):
67
92
  markcoroutinefunction(self)
68
93
 
69
- def __call__(self, request):
94
+ def __call__(
95
+ self, request: HttpRequest
96
+ ) -> Union[HttpResponse, Awaitable[HttpResponse]]:
70
97
  if iscoroutinefunction(self):
71
- return self.__acall__(request)
98
+ return cast(RequestMiddleware, self).__acall__(request) # type: ignore[redundant-cast,unused-ignore]
72
99
  self.prepare(request)
73
- response = self.get_response(request)
100
+ response = cast("HttpResponse", self.get_response(request))
74
101
  self.handle_response(request, response)
75
102
  return response
76
103
 
77
- async def __acall__(self, request):
104
+ async def __acall__(self, request: HttpRequest) -> HttpResponse:
78
105
  await sync.sync_to_async(self.prepare)(request)
79
106
  try:
80
- response = await self.get_response(request)
107
+ response = await cast(Awaitable["HttpResponse"], self.get_response(request))
81
108
  except asyncio.CancelledError:
82
109
  logger.warning("request_cancelled")
83
110
  raise
84
111
  await sync.sync_to_async(self.handle_response)(request, response)
85
112
  return response
86
113
 
87
- def handle_response(self, request, response):
114
+ def handle_response(self, request: HttpRequest, response: HttpResponse) -> None:
88
115
  if not hasattr(request, "_raised_exception"):
89
116
  self.bind_user_id(request)
90
117
  context = structlog.contextvars.get_merged_contextvars(logger)
@@ -114,7 +141,7 @@ class RequestMiddleware:
114
141
  if isinstance(response, StreamingHttpResponse):
115
142
  streaming_content = response.streaming_content
116
143
  try:
117
- iter(streaming_content)
144
+ iter(cast(Iterator[bytes], streaming_content))
118
145
  except TypeError:
119
146
  response.streaming_content = async_streaming_content_wrapper(
120
147
  streaming_content, context
@@ -136,7 +163,7 @@ class RequestMiddleware:
136
163
  )
137
164
  structlog.contextvars.clear_contextvars()
138
165
 
139
- def prepare(self, request):
166
+ def prepare(self, request: HttpRequest) -> None:
140
167
  from ipware import get_client_ip
141
168
 
142
169
  request_id = get_request_header(
@@ -161,11 +188,11 @@ class RequestMiddleware:
161
188
  logger.info("request_started", **log_kwargs)
162
189
 
163
190
  @staticmethod
164
- def format_request(request):
191
+ def format_request(request: HttpRequest) -> str:
165
192
  return f"{request.method} {request.get_full_path()}"
166
193
 
167
194
  @staticmethod
168
- def bind_user_id(request):
195
+ def bind_user_id(request: HttpRequest) -> None:
169
196
  user_id_field = app_settings.USER_ID_FIELD
170
197
  if hasattr(request, "user") and request.user is not None and user_id_field:
171
198
  user_id = None
@@ -175,7 +202,7 @@ class RequestMiddleware:
175
202
  user_id = str(user_id)
176
203
  structlog.contextvars.bind_contextvars(user_id=user_id)
177
204
 
178
- def process_exception(self, request, exception):
205
+ def process_exception(self, request: HttpRequest, exception: Exception) -> None:
179
206
  if isinstance(exception, (Http404, PermissionDenied)):
180
207
  # We don't log an exception here, and we don't set that we handled
181
208
  # an error as we want the standard `request_finished` log message
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-structlog
3
- Version: 8.1.0
3
+ Version: 9.0.0.dev1
4
4
  Summary: Structured Logging for Django
5
5
  Author-email: Jules Robichaud-Gagnon <j.robichaudg+pypi@gmail.com>
6
6
  License: MIT
@@ -13,17 +13,19 @@ Classifier: Development Status :: 5 - Production/Stable
13
13
  Classifier: Framework :: Django
14
14
  Classifier: Framework :: Django :: 4.2
15
15
  Classifier: Framework :: Django :: 5.0
16
+ Classifier: Framework :: Django :: 5.1
16
17
  Classifier: Programming Language :: Python :: 3
17
18
  Classifier: Programming Language :: Python :: 3 :: Only
18
- Classifier: Programming Language :: Python :: 3.8
19
19
  Classifier: Programming Language :: Python :: 3.9
20
20
  Classifier: Programming Language :: Python :: 3.10
21
21
  Classifier: Programming Language :: Python :: 3.11
22
22
  Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
23
24
  Classifier: Topic :: System :: Logging
24
25
  Classifier: License :: OSI Approved :: MIT License
25
26
  Classifier: Operating System :: OS Independent
26
- Requires-Python: >=3.8
27
+ Classifier: Typing :: Typed
28
+ Requires-Python: >=3.9
27
29
  Description-Content-Type: text/x-rst
28
30
  License-File: LICENSE.rst
29
31
  Requires-Dist: django>=4.2
@@ -6,6 +6,7 @@ django_structlog/__init__.py
6
6
  django_structlog/app_settings.py
7
7
  django_structlog/apps.py
8
8
  django_structlog/commands.py
9
+ django_structlog/py.typed
9
10
  django_structlog/signals.py
10
11
  django_structlog.egg-info/PKG-INFO
11
12
  django_structlog.egg-info/SOURCES.txt
@@ -10,7 +10,7 @@ build-backend = "setuptools.build_meta"
10
10
  ]
11
11
  readme = "README.rst"
12
12
  dynamic = ["version"]
13
- requires-python = ">=3.8"
13
+ requires-python = ">=3.9"
14
14
  license = { text = "MIT" }
15
15
  dependencies = [
16
16
  "django>=4.2",
@@ -23,16 +23,18 @@ build-backend = "setuptools.build_meta"
23
23
  "Framework :: Django",
24
24
  "Framework :: Django :: 4.2",
25
25
  "Framework :: Django :: 5.0",
26
+ "Framework :: Django :: 5.1",
26
27
  "Programming Language :: Python :: 3",
27
28
  "Programming Language :: Python :: 3 :: Only",
28
- "Programming Language :: Python :: 3.8",
29
29
  "Programming Language :: Python :: 3.9",
30
30
  "Programming Language :: Python :: 3.10",
31
31
  "Programming Language :: Python :: 3.11",
32
32
  "Programming Language :: Python :: 3.12",
33
+ "Programming Language :: Python :: 3.13",
33
34
  "Topic :: System :: Logging",
34
35
  "License :: OSI Approved :: MIT License",
35
36
  "Operating System :: OS Independent",
37
+ "Typing :: Typed",
36
38
  ]
37
39
 
38
40
  [project.urls]
@@ -62,11 +64,11 @@ build-backend = "setuptools.build_meta"
62
64
  [tool.black]
63
65
  line-length = 88
64
66
  target-version = [
65
- 'py38',
66
67
  'py39',
67
68
  'py310',
68
69
  'py311',
69
70
  'py312',
71
+ 'py313',
70
72
  ]
71
73
  include = '\.pyi?$'
72
74
  exclude = '''
@@ -84,7 +86,7 @@ build-backend = "setuptools.build_meta"
84
86
 
85
87
  [tool.ruff]
86
88
  line-length = 88
87
- target-version = "py312"
89
+ target-version = "py313"
88
90
  lint.ignore = [
89
91
  'E501',
90
92
  ]
@@ -99,17 +101,18 @@ build-backend = "setuptools.build_meta"
99
101
  #
100
102
  # Also, make sure that all python versions used here are included in ./github/worksflows/main.yml
101
103
  envlist =
102
- py{38,39,310,311}-django42-celery5{2,3}-redis{3,4}-kombu5,
103
- py31{0,1}-django50-celery53-redis4-kombu5,
104
- py312-django{42,50}-celery53-redis4-kombu5,
104
+ py{39,310,311}-django42-celery5{2,3}-redis{3,4}-kombu5,
105
+ py31{0,1}-django5{0,1}-celery5{3,4}-redis4-kombu5,
106
+ py312-django{42,50,51}-celery5{3,4}-redis4-kombu5,
107
+ py313-django{51}-celery5{3,4}-redis4-kombu5,
105
108
 
106
109
  [gh-actions]
107
110
  python =
108
- 3.8: py38
109
111
  3.9: py39
110
112
  3.10: py310
111
113
  3.11: py311
112
114
  3.12: py312
115
+ 3.13: py313
113
116
 
114
117
  [testenv]
115
118
  setenv =
@@ -125,8 +128,10 @@ build-backend = "setuptools.build_meta"
125
128
  celery51: Celery >=5.1, <5.2
126
129
  celery52: Celery >=5.2, <5.3
127
130
  celery53: Celery >=5.3, <5.4
131
+ celery54: Celery >=5.4, <5.5
128
132
  django42: Django >=4.2, <5.0
129
133
  django50: Django >=5.0, <5.1
134
+ django51: Django >=5.1, <5.2
130
135
  -r{toxinidir}/requirements/ci.txt
131
136
 
132
137
  commands = pytest --cov=./test_app --cov=./django_structlog --cov-append test_app
@@ -148,3 +153,22 @@ include = [
148
153
  "./django_structlog_demo_project/*",
149
154
  "./test_app/*",
150
155
  ]
156
+
157
+ [tool.mypy]
158
+ python_version=3.9
159
+ strict=true
160
+ packages=[
161
+ "django_structlog"
162
+ ]
163
+ disallow_subclassing_any=false
164
+
165
+ [[tool.mypy.overrides]]
166
+ module = [
167
+ "django_extensions.*",
168
+ "ipware",
169
+ "django.*",
170
+ "structlog.*",
171
+ "celery.*",
172
+ "asgiref.*",
173
+ ]
174
+ ignore_missing_imports = true