Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 55 additions & 4 deletions bugsnag/legacy.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Dict, Any, Tuple, Type, Optional, Union, List, Callable
import types
import sys
import warnings

from bugsnag.breadcrumbs import BreadcrumbType, OnBreadcrumbCallback
from bugsnag.feature_flags import FeatureFlag
Expand All @@ -13,16 +14,66 @@
ExcInfoType = Tuple[Type, Exception, types.TracebackType]


__all__ = ('configure', 'configure_request', 'add_metadata_tab',
'clear_request_config', 'notify', 'start_session', 'auto_notify',
'auto_notify_exc_info', 'before_notify', 'leave_breadcrumb')
__all__ = (
'configure',
'configure_request',
'add_metadata_tab',
'clear_request_config',
'notify',
'start_session',
'auto_notify',
'auto_notify_exc_info',
'before_notify',
'leave_breadcrumb',
)


def configure(**options):
"""
Configure the Bugsnag notifier application-wide settings.
"""
return configuration.configure(**options)
# Synchronize legacy references to point at the live configuration
# only `logger` is rebound in this scope
global logger

# Delegate to the module-local configuration instance so we update the
# single source of truth without resolving package submodules that may
# shadow the attribute name.
result = configuration.configure(**options)
try:
default_client.configuration = configuration
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default_client.configuration = configuration can replace the Client’s configuration object, but Client.__init__ wires session_tracker = SessionTracker(self.configuration) and SessionTracker holds its own config reference. If configuration and default_client.configuration ever differ (which this sync path is trying to handle), session delivery will continue using the old configuration unless default_client.session_tracker.config is updated (or the session tracker is recreated). Please keep these in sync to avoid sessions using stale API keys/endpoints/logger.

Suggested change
default_client.configuration = configuration
default_client.configuration = configuration
# Ensure the session tracker uses the live configuration as well.
# Client.__init__ wires `session_tracker = SessionTracker(self.configuration)`
# and SessionTracker stores its own `config` reference, so we need to
# keep it in sync when `default_client.configuration` is replaced.
session_tracker = getattr(default_client, "session_tracker", None)
if session_tracker is not None and hasattr(session_tracker, "config"):
session_tracker.config = configuration

Copilot uses AI. Check for mistakes.
except (AttributeError, TypeError, ImportError) as exc:
try:
configuration.logger.debug(
"legacy configuration sync failed: %s", exc
)
except Exception:
warnings.warn(
"legacy configuration sync failed: {}".format(exc),
stacklevel=2
)

logger = configuration.logger

# Also update the `bugsnag` package attributes so other modules that
# reference `bugsnag.configuration` / `bugsnag.logger` reflect the
# live configuration object (keeps package-level attributes in sync).
try:
import bugsnag as _pkg
setattr(_pkg, 'configuration', configuration)
setattr(_pkg, 'logger', configuration.logger)
except (ImportError, AttributeError, TypeError) as exc:
try:
configuration.logger.debug(
"legacy package attr sync failed: %s", exc
)
except Exception:
warnings.warn(
"legacy package attr sync failed: {}".format(exc),
stacklevel=2
)

return result


def configure_request(**options):
Expand Down
13 changes: 13 additions & 0 deletions tests/test_legacy_configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import bugsnag
from bugsnag import legacy


def test_legacy_configuration_sync():
"""After calling `bugsnag.configure(...)`, the package and legacy
configuration objects should point to the same live objects.
This prevents an import-time snapshot from becoming stale.
"""
bugsnag.configure(api_key="test-api-key")

assert bugsnag.configuration is legacy.configuration
assert getattr(bugsnag, "logger", None) is getattr(legacy, "logger", None)
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new test claims to assert that logger reflects the live instance, but it only checks bugsnag.logger is legacy.logger (and via getattr, it would also pass if either attribute were missing). To verify the regression, assert directly that bugsnag.logger (and/or legacy.logger) is the same object as bugsnag.configuration.logger after configure().

Suggested change
assert getattr(bugsnag, "logger", None) is getattr(legacy, "logger", None)
assert bugsnag.logger is bugsnag.configuration.logger
assert legacy.logger is bugsnag.configuration.logger

Copilot uses AI. Check for mistakes.
3 changes: 2 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ deps=
requests: requests
wsgi: webtest
asgi: starlette
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ASGI deps section now includes both asgi: starlette and asgi: starlette<0.28. This is redundant and can be confusing; consider removing the unpinned asgi: starlette entry and keeping only the constrained requirement.

Suggested change
asgi: starlette

Copilot uses AI. Check for mistakes.
asgi: starlette<0.28
asgi: requests
asgi: httpx
asgi: httpx<0.28
bottle: webtest
bottle: bottle
flask: flask
Expand Down
Loading