Skip to content
Snippets Groups Projects
Commit 67b5247d authored by Michael Fladischer's avatar Michael Fladischer
Browse files

New upstream version 9.0.1

parent 580f0727
No related branches found
No related tags found
No related merge requests found
Showing
with 568 additions and 87 deletions
......@@ -15,7 +15,7 @@ jobs:
strategy:
matrix:
python-version:
- '3.12'
- '3.13'
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
......@@ -34,19 +34,20 @@ jobs:
CELERY_BROKER_URL: redis://0.0.0.0:6379
DJANGO_SETTINGS_MODULE: config.settings.test_demo_app
run: pytest --cov=./django_structlog_demo_project --cov-append django_structlog_demo_project
- uses: codecov/codecov-action@v4
- uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false # disable for the moment because it prevents PR to succeed
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- '3.8'
- '3.9'
- '3.10'
- '3.11'
- '3.12'
- '3.13'
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
......@@ -58,14 +59,15 @@ jobs:
run: |
python -m pip install --upgrade pip
python -m pip install -U setuptools
python -m pip install tox tox-gh-actions
python -m pip install tox tox-gh-actions -r requirements/coverage.txt
- name: Start Redis
uses: supercharge/redis-github-action@1.8.0
- name: Test with tox
run: tox
- uses: codecov/codecov-action@v4
- uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false # disable for the moment because it prevents PR to succeed
test-docs:
needs:
......@@ -75,7 +77,7 @@ jobs:
strategy:
matrix:
python-version:
- '3.12'
- '3.13'
steps:
- uses: actions/checkout@v4
- name: Install dependencies
......@@ -97,7 +99,7 @@ jobs:
strategy:
matrix:
python-version:
- '3.12'
- '3.13'
steps:
- uses: actions/checkout@v4
- name: Install dependencies
......@@ -115,7 +117,7 @@ jobs:
strategy:
matrix:
python-version:
- '3.12'
- '3.13'
steps:
- uses: actions/checkout@v4
- name: Install dependencies
......@@ -125,13 +127,41 @@ jobs:
- name: run ruff
run: ruff check .
mypy:
needs:
- test-demo-app
- test
runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- '3.13'
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements/local.txt -r requirements/mypy.txt
- name: run mypy
run: mypy
isort:
needs:
- test-demo-app
- test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: isort/isort-action@v1
with:
requirements-files: "requirements/local-base.txt requirements/isort.txt"
test-install-base:
runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- '3.12'
- '3.13'
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
......@@ -157,7 +187,7 @@ jobs:
strategy:
matrix:
python-version:
- '3.12'
- '3.13'
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
......@@ -188,7 +218,7 @@ jobs:
strategy:
matrix:
python-version:
- '3.12'
- '3.13'
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
......@@ -220,6 +250,8 @@ jobs:
- test-install-commands
- black
- ruff
- mypy
- isort
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
......
......@@ -15,11 +15,11 @@ jobs:
- uses: actions/setup-python@v5
with:
python-version: 3.12
python-version: 3.13
- uses: browniebroke/pre-commit-autoupdate-action@v1.0.0
- uses: peter-evans/create-pull-request@v6.0.5
- uses: peter-evans/create-pull-request@v7.0.6
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: update/pre-commit-hooks
......
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
staticfiles/
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# Environments
.venv
venv/
ENV/
# Rope project settings
.ropeproject
# mkdocs documentation
/site
### Node template
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
### Linux template
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### VisualStudioCode template
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# Provided default Pycharm Run/Debug Configurations should be tracked by git
# In case of local modifications made by Pycharm, use update-index command
# for each changed file, like this:
# git update-index --assume-unchanged .idea/django_structlog_demo_project.iml
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/misc.xml
.idea/google-java-format.xml
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
# Gradle:
.idea/**/gradle.xml
.idea/**/libraries
# CMake
cmake-build-debug/
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
### Windows template
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
### macOS template
# General
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### SublimeText template
# Cache files for Sublime Text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
# Workspace files are user-specific
*.sublime-workspace
# Project files should be checked into the repository, unless a significant
# proportion of contributors will probably not be using Sublime Text
# *.sublime-project
# SFTP configuration file
sftp-config.json
# Package control specific files
Package Control.last-run
Package Control.ca-list
Package Control.ca-bundle
Package Control.system-ca-bundle
Package Control.cache/
Package Control.ca-certs/
Package Control.merged-ca-bundle
Package Control.user-ca-bundle
oscrypto-ca-bundle.crt
bh_unicode_properties.cache
# Sublime-github package stores a github token in this file
# https://packagecontrol.io/packages/sublime-github
GitHub.sublime-settings
### Vim template
# Swap
[._]*.s[a-v][a-z]
[._]*.sw[a-p]
[._]s[a-v][a-z]
[._]sw[a-p]
# Session
Session.vim
# Temporary
.netrwhist
# Auto-generated tag files
tags
### Project template
django_structlog_demo_project/media/
.pytest_cache/
.ipython/
.env
.envs/*
!.envs/.local/
.vscode/
......@@ -5,11 +5,11 @@
<option name="ourVersions">
<value>
<list size="5">
<item index="0" class="java.lang.String" itemvalue="3.8" />
<item index="1" class="java.lang.String" itemvalue="3.9" />
<item index="2" class="java.lang.String" itemvalue="3.10" />
<item index="3" class="java.lang.String" itemvalue="3.11" />
<item index="4" class="java.lang.String" itemvalue="3.12" />
<item index="0" class="java.lang.String" itemvalue="3.9" />
<item index="1" class="java.lang.String" itemvalue="3.10" />
<item index="2" class="java.lang.String" itemvalue="3.11" />
<item index="3" class="java.lang.String" itemvalue="3.12" />
<item index="4" class="java.lang.String" itemvalue="3.13" />
</list>
</value>
</option>
......
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.3
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
- repo: https://github.com/ambv/black
rev: 24.4.2
rev: 24.10.0
hooks:
- id: black
language_version: python3
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.4
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.14.1
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: mypy
args: [--no-incremental]
additional_dependencies: [
celery-types==0.22.0,
"django-stubs[compatible-mypy]==5.1.2",
structlog==24.4.0,
django-extensions==3.2.3,
django-ipware==7.0.1,
]
exclude: |
(?x)(
^django_structlog_demo_project/|
^config/|
^docs/|
^manage.py
)
......@@ -7,9 +7,9 @@ version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-22.04
os: ubuntu-24.04
tools:
python: "3.12"
python: "3.13"
# Build documentation in the docs/ directory with Sphinx
sphinx:
......
......@@ -5,8 +5,14 @@ django-structlog
| |pypi| |wheels| |build-status| |docs| |coverage| |open_issues| |pull_requests|
| |django| |python| |license| |black| |ruff|
| |django_packages|
| |watchers| |stars| |forks|
.. |django_packages| image:: https://img.shields.io/badge/Published%20on-Django%20Packages-0c3c26
:target: https://djangopackages.org/packages/p/django-structlog/
:alt: Published on Django Packages
.. |build-status| image:: https://github.com/jrobichaud/django-structlog/actions/workflows/main.yml/badge.svg?branch=main
:target: https://github.com/jrobichaud/django-structlog/actions
:alt: Build Status
......@@ -369,6 +375,44 @@ Json file (\ ``logs/json.log``\ )
Upgrade Guide
=============
.. _upgrade_9.0:
Upgrading to 9.0+
^^^^^^^^^^^^^^^^^
Minimum requirements
~~~~~~~~~~~~~~~~~~~~
- requires python 3.9+
- django 4.2 and 5.0+ are supported
Type hints
~~~~~~~~~~
``django-structlog`` now uses `python type hints <https://docs.python.org/3/library/typing.html>`_ and is being validated with `mypy <https://mypy.readthedocs.io/en/stable/>`_ ``--strict``.
For ``drf-standardized-errors`` users
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Now unhandled exceptions when using `drf-standardized-errors <https://github.com/ghazi-git/drf-standardized-errors>`_ will be intercepted and the exception logged properly.
If you also use `structlog-sentry <https://github.com/kiwicom/structlog-sentry>`_, the exception will now be propagated as expected.
Other libraries alike may be affected by this change.
Internal changes in how ``RequestMiddleware`` handles exceptions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This only affects you if you implemented a middleware inheriting from ``RequestMiddleware`` and you overrided the ``process_exception`` method.
Did you?
If so:
- ``RequestMiddleware.process_exception`` was renamed to ``RequestMiddleware._process_exception``, you should to the same in the middleware.
.. _upgrade_8.0:
Upgrading to 8.0+
......
......@@ -10,9 +10,6 @@ ARG REQUIREMENTS_FILE=local.txt
RUN <<EOF
apk update
# psycopg2 dependencies
apk add --virtual build-deps gcc python3-dev musl-dev
apk add postgresql-dev
# CFFI dependencies
apk add libffi-dev py-cffi
# https://docs.djangoproject.com/en/dev/ref/django-admin/#dbshell
......
......@@ -20,17 +20,17 @@ postgres_ready() {
python << END
import sys
import psycopg2
import psycopg
try:
psycopg2.connect(
psycopg.connect(
dbname="${POSTGRES_DB}",
user="${POSTGRES_USER}",
password="${POSTGRES_PASSWORD}",
host="${POSTGRES_HOST}",
port="${POSTGRES_PORT}",
)
except psycopg2.OperationalError:
except psycopg.OperationalError:
sys.exit(-1)
sys.exit(0)
......
......@@ -7,7 +7,7 @@ set -o nounset
# Basically we watch only README.rst, LICENCE.rst and everything under django_structlog
sphinx-autobuild docs /docs/_build/html \
-b ${SPHINX_COMMAND} \
--port 5000 \
--port 8080 \
--host 0.0.0.0 \
--watch . \
--ignore "*___jb_*" \
......
import structlog
from .base import * # noqa: F403
from .base import env, MIDDLEWARE
from .base import MIDDLEWARE, env
# GENERAL
# ------------------------------------------------------------------------------
......
......@@ -3,8 +3,8 @@ With these settings, tests run faster.
"""
import os
import environ
import environ
import structlog
env = environ.Env()
......
from django.conf import settings
from django.conf.urls import include
from django.urls import re_path
from django.conf.urls.static import static
from django.contrib import admin
from django.views.generic import TemplateView
from django.urls import re_path
from django.views import defaults as default_views
from django.views.generic import TemplateView
from django_structlog_demo_project.home import views, api_views, ninja_views
from django_structlog_demo_project.home import api_views, ninja_views, views
def uncaught_exception_view(request):
......
......@@ -3,6 +3,6 @@
name = "django_structlog"
VERSION = (8, 1, 0)
VERSION = (9, 0, 1)
__version__ = ".".join(str(v) for v in VERSION)
......@@ -8,19 +8,19 @@ class AppSettings:
PREFIX = "DJANGO_STRUCTLOG_"
@property
def CELERY_ENABLED(self):
def CELERY_ENABLED(self) -> bool:
return getattr(settings, self.PREFIX + "CELERY_ENABLED", False)
@property
def STATUS_4XX_LOG_LEVEL(self):
def STATUS_4XX_LOG_LEVEL(self) -> int:
return getattr(settings, self.PREFIX + "STATUS_4XX_LOG_LEVEL", logging.WARNING)
@property
def COMMAND_LOGGING_ENABLED(self):
def COMMAND_LOGGING_ENABLED(self) -> bool:
return getattr(settings, self.PREFIX + "COMMAND_LOGGING_ENABLED", False)
@property
def USER_ID_FIELD(self):
def USER_ID_FIELD(self) -> str:
return getattr(settings, self.PREFIX + "USER_ID_FIELD", "pk")
......
......@@ -6,7 +6,7 @@ from .app_settings import app_settings
class DjangoStructLogConfig(AppConfig):
name = "django_structlog"
def ready(self):
def ready(self) -> None:
if app_settings.CELERY_ENABLED:
from .celery.receivers import CeleryReceiver
......
from typing import TYPE_CHECKING, Any, Optional, Type, cast
import structlog
from celery import current_app
from celery.signals import (
before_task_publish,
after_task_publish,
before_task_publish,
task_failure,
task_prerun,
task_rejected,
task_retry,
task_success,
task_failure,
task_revoked,
task_success,
task_unknown,
task_rejected,
)
from . import signals
if TYPE_CHECKING: # pragma: no cover
from types import TracebackType
logger = structlog.getLogger(__name__)
class CeleryReceiver:
def __init__(self):
_priority: Optional[str]
def __init__(self) -> None:
self._priority = None
def receiver_before_task_publish(
self,
sender=None,
headers=None,
body=None,
properties=None,
routing_key=None,
**kwargs,
):
sender: Optional[Type[Any]] = None,
headers: Optional[dict[str, Any]] = None,
body: Optional[dict[str, str]] = None,
properties: Optional[dict[str, Any]] = None,
routing_key: Optional[str] = None,
**kwargs: dict[str, str],
) -> None:
if current_app.conf.task_protocol < 2:
return
......@@ -46,12 +52,16 @@ class CeleryReceiver:
)
if properties:
self._priority = properties.get("priority", None)
headers["__django_structlog__"] = context
cast(dict[str, Any], headers)["__django_structlog__"] = context
def receiver_after_task_publish(
self, sender=None, headers=None, body=None, routing_key=None, **kwargs
):
self,
sender: Optional[Type[Any]] = None,
headers: Optional[dict[str, Optional[str]]] = None,
body: Optional[dict[str, Optional[str]]] = None,
routing_key: Optional[str] = None,
**kwargs: Any,
) -> None:
properties = {}
if self._priority is not None:
properties["priority"] = self._priority
......@@ -59,13 +69,23 @@ class CeleryReceiver:
logger.info(
"task_enqueued",
child_task_id=headers.get("id") if headers else body.get("id"),
child_task_name=headers.get("task") if headers else body.get("task"),
child_task_id=(
headers.get("id")
if headers
else cast(dict[str, Optional[str]], body).get("id")
),
child_task_name=(
headers.get("task")
if headers
else cast(dict[str, Optional[str]], body).get("task")
),
routing_key=routing_key,
**properties,
)
def receiver_task_prerun(self, task_id, task, *args, **kwargs):
def receiver_task_prerun(
self, task_id: str, task: Any, *args: Any, **kwargs: Any
) -> None:
structlog.contextvars.clear_contextvars()
structlog.contextvars.bind_contextvars(task_id=task_id)
metadata = getattr(task.request, "__django_structlog__", {})
......@@ -75,10 +95,18 @@ class CeleryReceiver:
)
logger.info("task_started", task=task.name)
def receiver_task_retry(self, request=None, reason=None, einfo=None, **kwargs):
def receiver_task_retry(
self,
request: Optional[Any] = None,
reason: Optional[str] = None,
einfo: Optional[Any] = None,
**kwargs: Any,
) -> None:
logger.warning("task_retrying", reason=reason)
def receiver_task_success(self, result=None, **kwargs):
def receiver_task_success(
self, result: Optional[str] = None, **kwargs: Any
) -> None:
signals.pre_task_succeeded.send(
sender=self.receiver_task_success, logger=logger, result=result
)
......@@ -86,14 +114,14 @@ class CeleryReceiver:
def receiver_task_failure(
self,
task_id=None,
exception=None,
traceback=None,
einfo=None,
sender=None,
*args,
**kwargs,
):
task_id: Optional[str] = None,
exception: Optional[Exception] = None,
traceback: Optional["TracebackType"] = None,
einfo: Optional[Any] = None,
sender: Optional[Type[Any]] = None,
*args: Any,
**kwargs: Any,
) -> None:
throws = getattr(sender, "throws", ())
if isinstance(exception, throws):
logger.info(
......@@ -108,8 +136,13 @@ class CeleryReceiver:
)
def receiver_task_revoked(
self, request=None, terminated=None, signum=None, expired=None, **kwargs
):
self,
request: Any,
terminated: Optional[bool] = None,
signum: Optional[Any] = None,
expired: Optional[Any] = None,
**kwargs: Any,
) -> None:
metadata = getattr(request, "__django_structlog__", {}).copy()
metadata["task_id"] = request.id
metadata["task"] = request.task
......@@ -124,24 +157,31 @@ class CeleryReceiver:
)
def receiver_task_unknown(
self, message=None, exc=None, name=None, id=None, **kwargs
):
self,
message: Optional[str] = None,
exc: Optional[Exception] = None,
name: Optional[str] = None,
id: Optional[str] = None,
**kwargs: Any,
) -> None:
logger.error(
"task_not_found",
task=name,
task_id=id,
)
def receiver_task_rejected(self, message=None, exc=None, **kwargs):
def receiver_task_rejected(
self, message: Any, exc: Optional[Exception] = None, **kwargs: Any
) -> None:
logger.exception(
"task_rejected", task_id=message.properties.get("correlation_id")
)
def connect_signals(self):
def connect_signals(self) -> None:
before_task_publish.connect(self.receiver_before_task_publish)
after_task_publish.connect(self.receiver_after_task_publish)
def connect_worker_signals(self):
def connect_worker_signals(self) -> None:
before_task_publish.connect(self.receiver_before_task_publish)
after_task_publish.connect(self.receiver_after_task_publish)
task_prerun.connect(self.receiver_task_prerun)
......
import django.dispatch
bind_extra_task_metadata = django.dispatch.Signal()
""" Signal to add extra ``structlog`` bindings from ``celery``'s task.
......
from typing import Any
from celery import bootsteps
from .receivers import CeleryReceiver
......@@ -14,7 +16,7 @@ class DjangoStructLogInitStep(bootsteps.Step):
"""
def __init__(self, parent, **kwargs):
def __init__(self, parent: Any, **kwargs: Any) -> None:
super().__init__(parent, **kwargs)
self.receiver = CeleryReceiver()
self.receiver.connect_worker_signals()
import structlog
import uuid
from typing import TYPE_CHECKING, Any, List, Mapping, Tuple, Type
import structlog
from django_extensions.management.signals import ( # type: ignore[import-untyped]
post_command,
pre_command,
)
from django_extensions.management.signals import pre_command, post_command
if TYPE_CHECKING: # pragma: no cover
import contextvars
logger = structlog.getLogger(__name__)
class DjangoCommandReceiver:
def __init__(self):
stack: List[Tuple[str, Mapping[str, "contextvars.Token[Any]"]]]
def __init__(self) -> None:
self.stack = []
def pre_receiver(self, sender, *args, **kwargs):
def pre_receiver(self, sender: Type[Any], *args: Any, **kwargs: Any) -> None:
command_id = str(uuid.uuid4())
if len(self.stack):
parent_command_id, _ = self.stack[-1]
......@@ -26,13 +35,15 @@ class DjangoCommandReceiver:
command_name=sender.__module__.replace(".management.commands", ""),
)
def post_receiver(self, sender, outcome, *args, **kwargs):
def post_receiver(
self, sender: Type[Any], outcome: str, *args: Any, **kwargs: Any
) -> None:
logger.info("command_finished")
if len(self.stack): # pragma: no branch
command_id, tokens = self.stack.pop()
structlog.contextvars.reset_contextvars(**tokens)
def connect_signals(self):
def connect_signals(self) -> None:
pre_command.connect(self.pre_receiver)
post_command.connect(self.post_receiver)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment