Skip to content

Set default endpoints based on API key #399

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 24, 2025
Merged
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
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ Changelog

### Enhancements

* Remove depricated `datetime.utcnow()` method call from utils class
* Remove deprecated `datetime.utcnow()` method call from utils class
[#394](https://github.com/bugsnag/bugsnag-python/pull/394).
* Set default endpoints based on API key
[#399](https://github.com/bugsnag/bugsnag-php/pull/399)

## v4.7.1 (2024-05-22)

Expand Down
5 changes: 3 additions & 2 deletions bugsnag/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ class Client:
"""

def __init__(self, configuration: Optional[Configuration] = None,
install_sys_hook=True, **kwargs):
install_sys_hook=True, configure=True, **kwargs):
self.configuration = configuration or Configuration() # type: Configuration # noqa: E501
self.session_tracker = SessionTracker(self.configuration)
self.configuration.configure(**kwargs)
if configure:
self.configuration.configure(**kwargs)
self._context = ContextLocalState(self)
self._request_tracker = RequestTracker()

Expand Down
47 changes: 37 additions & 10 deletions bugsnag/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@
validate_int_setter,
validate_path_setter
)
from bugsnag.delivery import (create_default_delivery, DEFAULT_ENDPOINT,
DEFAULT_SESSIONS_ENDPOINT)
from bugsnag.delivery import (create_default_delivery,
DEFAULT_ENDPOINT,
DEFAULT_SESSIONS_ENDPOINT,
HUB_ENDPOINT,
HUB_SESSIONS_ENDPOINT)
from bugsnag.uwsgi import warn_if_running_uwsgi_without_threads
from bugsnag.error import Error

Expand All @@ -55,6 +58,11 @@
_sentinel = object()


def _is_hub_api_key(api_key: str) -> bool:
hub_prefix = "00000"
return api_key is not None and api_key.startswith(hub_prefix)


class Configuration:
"""
Global app-level Bugsnag configuration settings.
Expand Down Expand Up @@ -83,8 +91,8 @@ def __init__(self, logger=_sentinel):
"django.http.Http404",
"django.http.response.Http404",
]
self.endpoint = DEFAULT_ENDPOINT
self.session_endpoint = DEFAULT_SESSIONS_ENDPOINT
self.endpoint = None
self.session_endpoint = None
self.auto_capture_sessions = True
self.traceback_exclude_modules = []

Expand Down Expand Up @@ -126,8 +134,6 @@ def configure(self, api_key=None, app_type=None, app_version=None,
Validate and set configuration options. Will warn if an option is of an
incorrect type.
"""
if api_key is not None:
self.api_key = api_key
if app_type is not None:
self.app_type = app_type
if app_version is not None:
Expand All @@ -140,8 +146,6 @@ def configure(self, api_key=None, app_type=None, app_version=None,
self.auto_capture_sessions = auto_capture_sessions
if delivery is not None:
self.delivery = delivery
if endpoint is not None:
self.endpoint = endpoint
if hostname is not None:
self.hostname = hostname
if ignore_classes is not None:
Expand All @@ -162,8 +166,6 @@ def configure(self, api_key=None, app_type=None, app_version=None,
self.send_code = send_code
if send_environment is not None:
self.send_environment = send_environment
if session_endpoint is not None:
self.session_endpoint = session_endpoint
if traceback_exclude_modules is not None:
self.traceback_exclude_modules = traceback_exclude_modules
if logger is not _sentinel:
Expand All @@ -175,6 +177,11 @@ def configure(self, api_key=None, app_type=None, app_version=None,
if max_breadcrumbs is not None:
self.max_breadcrumbs = max_breadcrumbs

# Default endpoints depend on the API key
if api_key is not None:
self.api_key = api_key
self._initialize_endpoints(endpoint, session_endpoint, self.api_key)

return self

def get(self, name):
Expand Down Expand Up @@ -584,6 +591,26 @@ def _create_null_logger(self) -> logging.Logger:

return logger

def _initialize_endpoints(self, endpoint, session_endpoint, api_key):
# Default endpoints depending on the API key, if not already set
if (
endpoint is None and
session_endpoint is None and
self.endpoint is None and
self.session_endpoint is None
):
if _is_hub_api_key(api_key):
self.endpoint = HUB_ENDPOINT
self.session_endpoint = HUB_SESSIONS_ENDPOINT
else:
self.endpoint = DEFAULT_ENDPOINT
self.session_endpoint = DEFAULT_SESSIONS_ENDPOINT
# Do set endpoints if explicitly provided
if endpoint is not None:
self.endpoint = endpoint
if session_endpoint is not None:
self.session_endpoint = session_endpoint


class RequestConfiguration:
"""
Expand Down
8 changes: 6 additions & 2 deletions bugsnag/delivery.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@

DEFAULT_ENDPOINT = 'https://notify.bugsnag.com'
DEFAULT_SESSIONS_ENDPOINT = 'https://sessions.bugsnag.com'
HUB_ENDPOINT = 'https://notify.insighthub.smartbear.com'
HUB_SESSIONS_ENDPOINT = 'https://sessions.insighthub.smartbear.com'

__all__ = ('default_headers', 'Delivery')

Expand Down Expand Up @@ -82,8 +84,10 @@ def deliver_sessions(self, config, payload: Any, options=None):
"""
Sends sessions to Bugsnag
"""
if (config.endpoint != DEFAULT_ENDPOINT and config.session_endpoint ==
DEFAULT_SESSIONS_ENDPOINT):
if ((config.endpoint != DEFAULT_ENDPOINT and
config.session_endpoint == DEFAULT_SESSIONS_ENDPOINT) or
(config.endpoint != HUB_ENDPOINT and
config.session_endpoint == HUB_SESSIONS_ENDPOINT)):
Comment on lines +87 to +90
Copy link
Preview

Copilot AI Jun 20, 2025

Choose a reason for hiding this comment

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

[nitpick] Consider refactoring the compound condition in deliver_sessions for clarity. Splitting the check into separate conditional statements or adding inline comments explaining each branch would improve maintainability.

Suggested change
if ((config.endpoint != DEFAULT_ENDPOINT and
config.session_endpoint == DEFAULT_SESSIONS_ENDPOINT) or
(config.endpoint != HUB_ENDPOINT and
config.session_endpoint == HUB_SESSIONS_ENDPOINT)):
# Check if the session endpoint is not properly configured
is_default_endpoint_mismatch = (
config.endpoint != DEFAULT_ENDPOINT and
config.session_endpoint == DEFAULT_SESSIONS_ENDPOINT
)
is_hub_endpoint_mismatch = (
config.endpoint != HUB_ENDPOINT and
config.session_endpoint == HUB_SESSIONS_ENDPOINT
)
if is_default_endpoint_mismatch or is_hub_endpoint_mismatch:

Copilot uses AI. Check for mistakes.

if not self.sent_session_warning:
warnings.warn('The session endpoint has not been configured. '
'No sessions will be sent to Bugsnag.')
Expand Down
2 changes: 1 addition & 1 deletion bugsnag/legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from bugsnag.configuration import RequestConfiguration
from bugsnag.client import Client

default_client = Client()
default_client = Client(configure=False)
configuration = default_client.configuration
logger = configuration.logger
ExcInfoType = Tuple[Type, Exception, types.TracebackType]
Expand Down
23 changes: 22 additions & 1 deletion tests/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def test_hostname(self):
def test_session_tracking_defaults(self):
c = Configuration()
self.assertTrue(c.auto_capture_sessions)
self.assertEqual(c.session_endpoint, "https://sessions.bugsnag.com")
self.assertEqual(c.session_endpoint, None)

def test_default_middleware_location(self):
c = Configuration()
Expand Down Expand Up @@ -114,6 +114,16 @@ def test_validate_endpoint(self):
c.configure(endpoint='https://notify.example.com')
assert c.endpoint == 'https://notify.example.com'

def test_validate_endpoint_bugsnag_api_key(self):
c = Configuration()
c.configure(api_key='12312312312312312312312312321312')
assert c.endpoint == 'https://notify.bugsnag.com'

def test_validate_endpoint_hub_api_key(self):
c = Configuration()
c.configure(api_key='00000312312312312312312312321312')
assert c.endpoint == 'https://notify.insighthub.smartbear.com'

def test_validate_app_type(self):
c = Configuration()
assert c.app_type is None
Expand Down Expand Up @@ -410,6 +420,17 @@ def test_validate_session_endpoint(self):
c.configure(session_endpoint='https://sessions.example.com')
assert c.session_endpoint == 'https://sessions.example.com'

def test_validate_session_endpoint_bugsnag_api_key(self):
c = Configuration()
c.configure(api_key='12312312312312312312312312321312')
assert c.session_endpoint == 'https://sessions.bugsnag.com'

def test_validate_session_endpoint_hub_api_key(self):
c = Configuration()
c.configure(api_key='00000312312312312312312312321312')
assert (c.session_endpoint ==
'https://sessions.insighthub.smartbear.com')

def test_validate_traceback_exclude_modules(self):
c = Configuration()
with pytest.warns(RuntimeWarning) as record:
Expand Down